Android

How to get Picasa images using the Image Picker on Android devices running any OS version

Using the Image Picker to get a local image from the Gallery is pretty easy and trivial task. But things get a lot more interesting if the user has a Picasa account and he/she happens to select an image from one of their Picasa albums. If you do not handle this scenario, your app will crash! And there is no way for you to tell the Image Picker to show just local files. So, you have to handle it, or you will be releasing a buggy application!

Things got even more interesting after the release of Honeycomb! All of a sudden the code that was fetching Picasa images and was working flawlessly started failing on devices running OS 3.0 and up. After some investigation I found the culprit- Google changed the URI returned when the user was selecting a Picasa image. This change was completely undocumented, or at least I could not find any documentation on this! So, on devices running Android OS prior to 3.0 the URI returned was an actual URL and now on devices running OS 3.0 and higher, the URI returned had a different format. For example:

1. https://lh4.googleusercontent.com/… (URI returned on devices running OS prior to 3.0)
2. content://com.google.android.gallery3d (URI returned on devices running OS 3.0 and higher)

I posted a brief solution on the official Android bug report site, but I will expand on it here.

In order to properly handle fetching an image from the Gallery you need to handle three scenarios:

1. The user selected a local image file
2. The user selected a Picasa image and the device is running Android version prior to 3.0
3. The user selected a Picasa image and the device is running Android version 3.0 and higher

Let’s delve straight into the code:

1. Start the Image Picker:


private static final int PICTURE_GALLERY = 1;
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(photoPickerIntent, PICTURE_GALLERY);

In the first line above I am passing a URI to the intent because I would like to fetch the full image, not just get a Bitmap with the thumbnail. The Android team did a good job of acknowledging that if you just wanted a thumbnail of the image then you can get back a Bitmap object containing it, since a thumbnail would not be that big in size. But if you wanted the full image, it would be foolish to do the same, since that would take up a big chunk of the heap! So, what you get instead is some data about the image file, among which is the path to it.

2. We need to implement the onActivityResult method that will be called when the user closes (picks the image) the Image Gallery application:


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
	super.onActivityResult(requestCode, resultCode, intent);
	switch (requestCode) {
		case PICTURE_GALLERY:
		if (resultCode == RESULT_OK && intent != null) {
			Uri selectedImage = intent.getData();
			final String[] filePathColumn = { MediaColumns.DATA, MediaColumns.DISPLAY_NAME };
			Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
			// some devices (OS versions return an URI of com.android instead of com.google.android
			if (selectedImage.toString().startsWith("content://com.android.gallery3d.provider"))  {
				// use the com.google provider, not the com.android provider.
				selectedImage = Uri.parse(selectedImage.toString().replace("com.android.gallery3d","com.google.android.gallery3d"));
			}
			if (cursor != null) {
				cursor.moveToFirst();
				int columnIndex = cursor.getColumnIndex(MediaColumns.DATA);
				// if it is a picasa image on newer devices with OS 3.0 and up
				if (selectedImage.toString().startsWith("content://com.google.android.gallery3d")){
					columnIndex = cursor.getColumnIndex(MediaColumns.DISPLAY_NAME);
					if (columnIndex != -1) {
						progress_bar.setVisibility(View.VISIBLE);
						final Uri uriurl = selectedImage;
						// Do this in a background thread, since we are fetching a large image from the web
						new Thread(new Runnable() {
							public void run() {
								Bitmap the_image = getBitmap("image_file_name.jpg", uriurl);
							}
						}).start();
					}
				} else { // it is a regular local image file
					String filePath = cursor.getString(columnIndex);
					cursor.close();
					Bitmap the_image = decodeFile(new File(filePath));
				}
			}
			// If it is a picasa image on devices running OS prior to 3.0
			else if (selectedImage != null && selectedImage.toString().length() > 0) {
				progress_bar.setVisibility(View.VISIBLE);
				final Uri uriurl = selectedImage;
				// Do this in a background thread, since we are fetching a large image from the web
				new Thread(new Runnable() {
					public void run() {
						Bitmap the_image = getBitmap("image_file_name.jpg", uriurl);
					}
				}).start();
			}
		}
		break;
	}
}

3. Implement the getBitmap method, which will download the image from the web if the user selected a Picasa image:


private Bitmap getBitmap(String tag, Uri url)
{
	File cacheDir;
	// if the device has an SD card
	if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
		cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),".OCFL311");
	} else {
		// it does not have an SD card
	   	cacheDir=ActivityPicture.this.getCacheDir();
	}
	if(!cacheDir.exists())
	    	cacheDir.mkdirs();

	File f=new File(cacheDir, tag);

	try {
		Bitmap bitmap=null;
		InputStream is = null;
		if (url.toString().startsWith("content://com.google.android.gallery3d")) {
			is=getContentResolver().openInputStream(url);
		} else {
			is=new URL(url.toString()).openStream();
		}
		OutputStream os = new FileOutputStream(f);
		Utils.CopyStream(is, os);
		os.close();
		return decodeFile(f);
	} catch (Exception ex) {
		Log.d(Utils.DEBUG_TAG, "Exception: " + ex.getMessage());
		// something went wrong
		ex.printStackTrace();
		return null;
	}
}

The decodeFile method takes a reference to a file and returns a Bitmap. This is pretty trivial. In my case I also scale down the image as I am reading it from the file, so it does not take much memory.

I think Google has some work to do about all this and if they want to seamlessly integrate Picasa with the Android Gallery application, they should have done a better job with the Image Picker functionality. The developer should not be doing all this heavy lifting and be concerned with where the actual image resides. The returned URI should be exactly the same no matter if the image is local or not. And if it is not local, the Gallery app should be fetching it for us. That way, we will have a consistent and predictable functionality. Right now you will be surprised how many applications out there do not handle Picasa images and crash with an ugly error.

Thursday, March 1st, 2012 Android, Eclipse 1 Comment

How to Switch your Google Checkout Account in the Android Market Application

With Google’s last update of the Market application on the Android devices, it became a lot easier to now switch your Google Checkout account associated with all your application purchases.

Just fire up the Market application on your device and press the Menu button. That gives you the “Accounts” option, which allows you to switch the Google Checkout account associated with your Market application:

Of course you have to have added your new Google account to the available accounts on the phone before you can do the above switch. To do this, go to:

Menu -> Settings -> Accounts & sync

and add your account (new Gmail email) here. Then you can go to the Market application and switch to this new account.

In the past this used to require a factory reset of the device or a root access.

Although this is a great feature for the consumer, it might also turn out to be an issue in the long run. All downloaded/purchased applications are associated with the Google Checkout account used at the time of acquiring them from the Market. So if you switch your account in the Market application, you will no longer have access to the applications you purchases or downloaded with the previous account. For example, when you go to “My Apps” in the Market application, you will not be able to see the application you downloaded with your old account.

Furthermore (unless Google is doing something new that I am not aware of) all the licensed application you purchased from the Market with the old account will begin to complain that they are not licensed for your new account.

Monday, August 15th, 2011 Android 1 Comment

How to sign an unsigned Android package (.apk file)

If you develop with Eclipse, you most likely use the built in Export Wizard to export and sign your Android applications.

There are some cases though, when this method will not do. For example, if you decide to publish your applications on the new Amazon App Store, you will find out that they require you to submit an unsigned apk first. They do some optimizations and DRM (if you chose to use it) processing of it, and then they allow you to download the new package and sign and re-upload the final .apk file.

Amazon provides an option to sign the package for you, but in a lot of cases that will not work. For example, if you use some Google API’s (like Google Maps, etc.) you must sign it yourself! Otherwise the application will not work!

Steps to sign your application:

1. Export the unsigned package:

Right click on the project in Eclipse -> Android Tools -> Export Unsigned Application Package

2. Sign the application using your keystore and the jarsigner tool (comes with the JDK):

Change directory to where your unsigned .apk file is. Then run:

jarsigner -verbose -keystore /path_to_keystore/mykeystore.keystore my_application.apk my_keystore_alias

It will ask you to provide your password:

Enter Passphrase for keystore:

Once you enter the password it will sign your apk. To verify that the signing is successful you can run:

jarsigner -verify my_application.apk

It should come back with:

jar verified.

Just an FYI: The jarsigner tool should be in your /usr/bin directory by default.

Here is a detailed documentation on signing your Android applications: http://developer.android.com/guide/publishing/app-signing.html

3. Do not forget to zipalign the .apk at the very end!

Even though this is not absolutely necessary, it is highly recommended. The zipalign tool optimizes the .apk file and makes it a lot faster to execute.

To zipalign your application:

zipalign -f -v 4 my_application.apk my_zipaligned_application.apk

As you can see, zipalign expects you to provide the input .apk file and specify what you want the output file to be named.

Zipalign tool documentation.

Friday, April 29th, 2011 Android, Eclipse, Programming 3 Comments

Eclipse Project is Flagged with an “Android Packaging Problem” Error

Sometimes while developing in Eclipse you will notice that the Android Project will be flagged with the red “x” but none of your source files or resources will have errors. If you look in the “Problems” tab you will notice that the project is flagged with an “Android Package Problem” type and the “Location” will be Unknown.

To fix this, just do:

Project->Clean

This will rebuild the project from scratch.

Friday, January 28th, 2011 Android, Eclipse, Java, Programming 2 Comments

Android Version 2.2.2 Prevents Applications with Copy Protection Turned on from Displaying in the Market

Today I got the Over the Air update of my Nexus One phone to version 2.2.2. To my surprise I could not see some of my applications that were published in the Android Market. Neither could I see a big chunk of the total applications in the Android Market.

After some tests and digging it turned out that applications that have the “Copy Protection” turned on in the “Developer Console” would not be displayed in the Android Market on devices running version 2.2.2.

I have not tested this on devices with ver. 2.3 and 3.0, but I would suspect that the result would be the same.

For a long while the “Copy Protection” feature has been marked as “will be deprecated soon” by Google. No date or any other pointer has been published by Google as to when that will be. I guess we got the answer with this last OS update.

If you want your application to be available to all the devices running the latest OS versions, you will have to turn off the copy protection feature. Of course if your application is paid you would still want to make sure that only people that have purchased it will be able to run it. To do that, just implement the licensing service in your application.

Monday, January 24th, 2011 Android 1 Comment

Search

 

Archive

May 2012
M T W T F S S
« Mar    
 123456
78910111213
14151617181920
21222324252627
28293031  

Other