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.

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

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

  • March 25, 2012 at 11:20 pm
    Permalink

    Hi Dimitar Darazhansk!
    I use link com.android.sec.gallery3d to get image from picasa.
    But maybe it got image not from picasa web. Image after decode was broken. (like thumbnail)

    Can you help me! Thank alot!

  • June 10, 2012 at 11:40 am
    Permalink

    Hi Dimitar,

    Thanks for that neat solution. I struggled to find a fix for that weird Picasa loader. I’ve implemented yours and it works fine.

    For anyone implementing that:

    – Utils.CopyStream(is, os) is not provided and can be replaced by:
    byte[] buffer = new byte[1024];
    int len;
    while ((len = is.read(buffer)) != -1) {
    os.write(buffer, 0, len);
    }

    – I also get URI starting with “content://com.android.sec.gallery3d” instead of “content://com.google.android.gallery3d”. So I added a second string comparison test to handle that URI

  • August 19, 2012 at 3:38 am
    Permalink

    Thanks for the post, but wouldn’t be nice if you can include all the files as download. Thank you.

  • October 1, 2012 at 2:54 am
    Permalink

    I create a local file with uri i keep track of and set it like so. It works with picasa and is a lot cleaner

    cameraIntent.putExtra( android.provider.MediaStore.EXTRA_OUTPUT, outuri);

  • January 7, 2013 at 5:56 pm
    Permalink

    Thanks very much for writing this post. It pretty much solved my Picasa problem.

    I did find that, in your getBitmap() method, getContentResolver().openInputStream(url) kept returning an empty stream on my Galaxy SIII test phone. It was a non-null stream, just with no data. I spent some time Googling around, and I found two possible solutions:

    Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(getContentResolver(), url);
    // Returns the image directly as a bitmap, no need for extra decode step

    or

    BitmapFactory.Options options = new BitmapFactory.Options();
    android.content.res.AssetFileDescriptor fd = getContentResolver().openAssetFileDescriptor(url, “r”);
    Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
    fd.close();
    // Get at Bitmap decoding of the image directly in case you need to play will options (like reducing the file size)

    Hopefully this will save some people from headaches in the future. Thanks again!

  • January 9, 2013 at 10:39 am
    Permalink

    Joel thanks for sharing that solution, it works =)… just to clarify on this case there is no need to replace “content://com.sec.android.gallery3d” for “content://com.google.android.gallery3d”

  • April 5, 2013 at 4:25 am
    Permalink

    Hi,

    thanks a lot, this will help me to fix a bug. Are you aware of a library that handles the “load image from intent” stuff? Because there are other specialities to handle, if you select an image from DropBox for example.

    If not, do you mind, if I include your code in such a library and open source it?

    Andreas

  • April 9, 2013 at 9:47 am
    Permalink

    @Andreas,

    Sorry for the delayed response… but yes.. go ahead an include the code anywhere you think it might be useful!

  • April 9, 2013 at 10:36 am
    Permalink

    Thank’s a lot,

    my problem was fixed. That can filter the URI from DropBox to.
    I just filter the URI for android.gallery3d and that’s running well till now.

  • April 11, 2013 at 5:21 am
    Permalink

    Thank you for sharing your info. I truly appreciate your efforts and I
    will be waiting for your further post thanks once again.

  • May 12, 2013 at 11:41 pm
    Permalink

    Truly when someone doesn’t be aware of then its up to other people that they will help, so here it occurs.

  • August 30, 2013 at 3:38 am
    Permalink

    POST A ZIP FOR GODS SAKE….what is wrong with ppl?

  • August 31, 2013 at 1:35 pm
    Permalink

    This is just what I needed. Thankyou for this.

  • January 31, 2014 at 12:42 am
    Permalink

    Hi,
    Thank you very much for sharing such type of great thought.
    I can’t see Picasa/Flickr options on my devices . I see options of Sky-drive and Google drive but I unable to access images into my apps from Sky-drive/ Google Drive .
    Could you please help me ?
    Thank you very much.

    Regards,
    Maidul Islam

  • March 13, 2014 at 11:01 am
    Permalink

    So, how about videos?

  • March 17, 2014 at 2:11 am
    Permalink

    Generally I do not learn post on blogs, however
    I wish to say that this write-up very forced me to try and do it!
    Your writing style has been surprised me. Thanks, quite great post.

  • July 2, 2014 at 10:42 pm
    Permalink

    Maybe I’m dense: what provide Utils.* and what provides ActivityPicture?

  • December 23, 2014 at 10:33 pm
    Permalink

    I have read so many articles or reviews about the blogger
    lovers but this article is really a pleasant article, keep it up.

  • Pingback:Fixed Get/pick an image from Android's built-in Gallery app programmatically #dev #it #asnwer | Good Answer

  • Pingback:Fix: Get/pick an image from Android's built-in Gallery app programmatically #computers #programming #dev | IT Info

  • May 26, 2015 at 4:19 am
    Permalink

    Finally ended with a classic solution…By using Document.util, which covers all authority :-

    1-Inside your onActivityResult():-
    case GALLERY_CAPTURE_IMAGE_REQUEST_CODE:
    String filePath = DocumentUtils.getPath(MainContainerActivity.this,data.getData();

    break;

    2- Create class DocumentUtils:-

    public class DocumentUtils {

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
    // ExternalStorageProvider
    if (isExternalStorageDocument(uri)) {
    final String docId = DocumentsContract.getDocumentId(uri);
    final String[] split = docId.split(“:”);
    final String type = split[0];

    if (“primary”.equalsIgnoreCase(type)) {
    return Environment.getExternalStorageDirectory() + “/” + split[1];
    }

    // TODO handle non-primary volumes
    }
    // DownloadsProvider
    else if (isDownloadsDocument(uri)) {

    final String id = DocumentsContract.getDocumentId(uri);
    final Uri contentUri = ContentUris.withAppendedId(Uri.parse(“content://downloads/public_downloads”), Long.valueOf(id));

    return getDataColumn(context, contentUri, null, null);
    }
    // MediaProvider
    else if (isMediaDocument(uri)) {
    final String docId = DocumentsContract.getDocumentId(uri);
    final String[] split = docId.split(“:”);
    final String type = split[0];

    Uri contentUri = null;
    if (“image”.equals(type)) {
    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    } else if (“video”.equals(type)) {
    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    } else if (“audio”.equals(type)) {
    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    }

    final String selection = “_id=?”;
    final String[] selectionArgs = new String[]{split[1]};

    return getDataColumn(context, contentUri, selection, selectionArgs);
    }
    }
    // MediaStore (and general)
    else if (“content”.equalsIgnoreCase(uri.getScheme())) {
    // Return the remote address
    String url;
    if (isGooglePhotosUri(uri)) {
    url = getDataColumnWithAuthority(context, uri, null, null);
    return getDataColumn(context, Uri.parse(url), null, null);
    }

    return getDataColumn(context, uri, null, null);

    }
    // File
    else if (“file”.equalsIgnoreCase(uri.getScheme())) {
    return uri.getPath();
    }

    return null;
    }

    /**
    * Get the value of the data column for this Uri. This is useful for
    * MediaStore Uris, and other file-based ContentProviders.
    *
    * @param context The context.
    * @param uri The Uri to query.
    * @param selection (Optional) Filter used in the query.
    * @param selectionArgs (Optional) Selection arguments used in the query.
    * @return The value of the _data column, which is typically a file path.
    */
    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

    Cursor cursor = null;
    final String column = “_data”;
    final String[] projection = {column};

    try {
    cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
    if (cursor != null && cursor.moveToFirst()) {
    final int column_index = cursor.getColumnIndexOrThrow(column);
    return cursor.getString(column_index);
    }
    } finally {
    if (cursor != null)
    cursor.close();
    }
    return null;
    }
    /**
    * Function for fixing synced folder image picking bug
    *
    * **/
    public static String getDataColumnWithAuthority(Context context, Uri uri, String selection, String[] selectionArgs) {
    Bitmap bitmap = null;
    InputStream is = null;
    if (uri.getAuthority()!=null){
    try {
    is = context.getContentResolver().openInputStream(uri);
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }
    Bitmap bmp = BitmapFactory.decodeStream(is);
    return ImageLoader.getImageUri(context,bmp).toString();
    }

    return null;
    }

    /**
    * @param uri The Uri to check.
    * @return Whether the Uri authority is ExternalStorageProvider.
    */
    public static boolean isExternalStorageDocument(Uri uri) {
    return “com.android.externalstorage.documents”.equals(uri.getAuthority());
    }

    /**
    * @param uri The Uri to check.
    * @return Whether the Uri authority is DownloadsProvider.
    */
    public static boolean isDownloadsDocument(Uri uri) {
    return “com.android.providers.downloads.documents”.equals(uri.getAuthority());
    }

    /**
    * @param uri The Uri to check.
    * @return Whether the Uri authority is MediaProvider.
    */
    public static boolean isMediaDocument(Uri uri) {
    return “com.android.providers.media.documents”.equals(uri.getAuthority());
    }

    /**
    * @param uri The Uri to check.
    * @return Whether the Uri authority is Google Photos.
    */
    public static boolean isGooglePhotosUri(Uri uri) {
    if(uri.getAuthority()!=null)
    return true;
    return false;
    }

    }

    3- Also you will need following function in ImageLoader.java:-

    public static Uri getImageUri(Context inContext, Bitmap inImage) {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, “Title”, null);
    return Uri.parse(path);
    }

  • January 25, 2017 at 1:22 am
    Permalink

    Please give downloading link

Leave a Reply

Your email address will not be published. Required fields are marked *

*