Before KitKat (or before the new Gallery) the Intent.ACTION_GET_CONTENT
returned a URI like this
content://media/external/images/media/39
For those who is still using @Paul Burke's code with Android SDK version 23 and above, if your project met the error saying that you are missing EXTERNAL_PERMISSION, and you are very sure you have already added user-permission in your AndroidManifest.xml file. That's because you may in Android API 23 or above and Google make it necessary to guarantee permission again while you make the action to access the file in runtime.
That means: If your SDK version is 23 or above, you are asked for READ & WRITE permission while you are selecting the picture file and want to know the URI of it.
And following is my code, in addition to Paul Burke's solution. I add these code and my project start to work fine.
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSINOS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
public static void verifyStoragePermissions(Activity activity) {
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
activity,
PERMISSINOS_STORAGE,
REQUEST_EXTERNAL_STORAGE
);
}
}
And in your activity&fragment where you are asking for the URI:
private void pickPhotoFromGallery() {
CompatUtils.verifyStoragePermissions(this);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
// startActivityForResult(intent, REQUEST_PHOTO_LIBRARY);
startActivityForResult(Intent.createChooser(intent, "选择照片"),
REQUEST_PHOTO_LIBRARY);
}
In my case, CompatUtils.java is where I define the verifyStoragePermissions method (as static type so I can call it within other activity).
Also it should make more sense if you make an if state first to see whether the current SDK version is above 23 or not before you call the verifyStoragePermissions method.
The answer to your question is that you need to have permissions. Type the following code in your manifest.xml file:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
<uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`
It worked for me...
This worked fine for me:
else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK)
{
Uri uri = data.getData();
Log.i(TAG, "old uri = " + uri);
dumpImageMetaData(uri);
try {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Log.i(TAG, "File descriptor " + fileDescriptor.toString());
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
options.inSampleSize =
BitmapHelper.calculateInSampleSize(options,
User.PICTURE_MAX_WIDTH_IN_PIXELS,
User.PICTURE_MAX_HEIGHT_IN_PIXELS);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
imageViewPic.setImageBitmap(bitmap);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
// get byte array here
byte[] picData = stream.toByteArray();
ParseFile picFile = new ParseFile(picData);
user.setProfilePicture(picFile);
}
catch(FileNotFoundException exc)
{
Log.i(TAG, "File not found: " + exc.toString());
}
}
This is a total hack, but here's what I did...
So while playing with setting up a DocumentsProvider, I noticed that the sample code (in getDocIdForFile
, around line 450) generates a unique id for a selected document based on the file's (unique) path relative to the specified root you give it (that is, what you set mBaseDir
to on line 96).
So the URI ends up looking something like:
content://com.example.provider/document/root:path/to/the/file
As the docs say, it's assuming only a single root (in my case that's Environment.getExternalStorageDirectory()
but you may use somewhere else... then it takes the file path, starting at the root, and makes it the unique ID, prepending "root:
". So I can determine the path by eliminating the "/document/root:
" part from uri.getPath(), creating an actual file path by doing something like this:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check resultcodes and such, then...
uri = data.getData();
if (uri.getAuthority().equals("com.example.provider")) {
String path = Environment.getExternalStorageDirectory(0.toString()
.concat("/")
.concat(uri.getPath().substring("/document/root:".length())));
doSomethingWithThePath(path); }
else {
// another provider (maybe a cloud-based service such as GDrive)
// created this uri. So handle it, or don't. You can allow specific
// local filesystem providers, filter non-filesystem path results, etc.
}
I know. It's shameful, but it worked. Again, this relies on you using your own documents provider in your app to generate the document ID.
(Also, there's a better way to build the path that don't assume "/" is the path separator, etc. But you get the idea.)
Had the same problem, tried the solution above but though it worked generally, for some reason I was getting permission denial on Uri content provider for some images although I had the android.permission.MANAGE_DOCUMENTS
permission added properly.
Anyway found other solution which is to force opening image gallery instead of KITKAT documents view with :
// KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);
and then load the image:
Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);
EDIT
ACTION_OPEN_DOCUMENT
might require you to persist permissions flags etc and generally often results in Security Exceptions...
Other solution is to use the ACTION_GET_CONTENT
combined with c.getContentResolver().openInputStream(selectedImageURI)
which will work both on pre-KK and KK. Kitkat will use new documents view then and this solution will work with all apps like Photos, Gallery, File Explorer, Dropbox, Google Drive etc...) but remember that when using this solution you have to create image in your onActivityResult()
and store it on SD Card for example. Recreating this image from saved uri on next app launch would throw Security Exception on content resolver even when you add permission flags as described in Google API docs (that's what happened when I did some testing)
Additionally the Android Developer API Guidelines suggest:
ACTION_OPEN_DOCUMENT is not intended to be a replacement for ACTION_GET_CONTENT. The one you should use depends on the needs of your app:
Use ACTION_GET_CONTENT if you want your app to simply read/import data. With this approach, the app imports a copy of the data, such as an image file.
Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider. An example would be a photo-editing app that lets users edit images stored in a document provider.
If anyone's interested, I made a working Kotlin version for ACTION_GET_CONTENT
:
var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if (path.contains("/document/image:")) { // files selected from "Documents"
databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
selection = "_id=?"
selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
} else { // files selected from all other sources, especially on Samsung devices
databaseUri = uri
selection = null
selectionArgs = null
}
try {
val projection = arrayOf(MediaStore.Images.Media.DATA,
MediaStore.Images.Media._ID,
MediaStore.Images.Media.ORIENTATION,
MediaStore.Images.Media.DATE_TAKEN) // some example data you can query
val cursor = context.contentResolver.query(databaseUri,
projection, selection, selectionArgs, null)
if (cursor.moveToFirst()) {
// do whatever you like with the data
}
cursor.close()
} catch (e: Exception) {
Log.e(TAG, e.message, e)
}