Keywords: Android | KitKat | URI Handling | Compatibility | ContentResolver
Abstract: This article explores the URI changes introduced in Android 4.4 KitKat for Intent.ACTION_GET_CONTENT and their impact on app development. By analyzing code examples from the best answer, it explains how to handle different URI formats through version detection, permission management, and ContentResolver queries. The discussion includes when to use ACTION_OPEN_DOCUMENT versus ACTION_GET_CONTENT, with a complete implementation ensuring compatibility across KitKat and earlier versions.
Introduction
In Android app development, handling user-selected images is a common requirement. Traditionally, developers use Intent.ACTION_GET_CONTENT to launch the gallery and query the MediaStore.Images.Media.DATA field via ContentResolver to obtain file paths. However, in Android 4.4 KitKat, this mechanism changed, leading to inconsistent URI formats and compatibility challenges for developers.
URI Changes in KitKat
Prior to KitKat, Intent.ACTION_GET_CONTENT typically returned URIs in formats like content://media/external/images/media/3951, allowing direct queries for the _data field. In KitKat, the new document provider framework introduced different URI formats, such as content://com.android.providers.media.documents/document/image:3951. This change requires developers to adjust their code to handle the new format correctly.
Compatibility Handling Solution
To ensure apps work correctly on KitKat and earlier versions, best practices involve using different Intents based on the Android version. The following code demonstrates version detection and corresponding Intent calls:
if (Build.VERSION.SDK_INT < 19) {
Intent intent = new Intent();
intent.setType("image/jpeg");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)), GALLERY_INTENT_CALLED);
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/jpeg");
startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}
In onActivityResult, handle the returned URI and ensure persistent permissions are obtained on KitKat:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) return;
if (null == data) return;
Uri originalUri = null;
if (requestCode == GALLERY_INTENT_CALLED) {
originalUri = data.getData();
} else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
originalUri = data.getData();
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
}
loadSomeStreamAsynkTask(originalUri);
}
URI Parsing and File Path Retrieval
For obtained URIs, developers may need to convert them to file paths. On KitKat, use the DocumentsContract class to parse document URIs. Here is a helper method to get file paths from URIs, supporting multiple ContentProviders:
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
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];
}
} 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);
} 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);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}
return getDataColumn(context, uri, null, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
This method adapts to various providers like external storage, downloads, and media by checking URI authority and scheme, using the getDataColumn helper to query the _data field.
Alternative Approaches and Considerations
Beyond ACTION_OPEN_DOCUMENT, developers can use Intent.ACTION_PICK to directly launch the gallery, though this may limit user selection from other document providers. Alternatively, reading URI streams directly via ContentResolver.openInputStream is a simple and effective method for scenarios not requiring file paths:
InputStream input = cxt.getContentResolver().openInputStream(fileUri);
Bitmap bmp = BitmapFactory.decodeStream(input);
According to Android development guidelines, ACTION_GET_CONTENT is suitable for simple data import, while ACTION_OPEN_DOCUMENT is better for apps needing long-term document access. Developers should choose based on app requirements, handling permissions and exceptions carefully.
Conclusion
Handling URI changes in Android 4.4 KitKat requires version adaptation and permission management strategies. By combining ACTION_OPEN_DOCUMENT, DocumentsContract, and ContentResolver, developers can build robust, user-friendly image selection features. The code examples and parsing methods provided in this article offer practical guidance for addressing this common compatibility challenge.