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.
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!
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
Thanks for the post, but wouldn’t be nice if you can include all the files as download. Thank you.
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);
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!
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”
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
@Andreas,
Sorry for the delayed response… but yes.. go ahead an include the code anywhere you think it might be useful!
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.
Thank you for sharing your info. I truly appreciate your efforts and I
will be waiting for your further post thanks once again.
Truly when someone doesn’t be aware of then its up to other people that they will help, so here it occurs.
POST A ZIP FOR GODS SAKE….what is wrong with ppl?
This is just what I needed. Thankyou for this.
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
So, how about videos?
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.
Maybe I’m dense: what provide Utils.* and what provides ActivityPicture?
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
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);
}
Please give downloading link