My Android app opens images and makes manipulations on them. My Activity requests an image to be opened as follows:
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,
"Complete action using"),
PICK_FROM_FILE);
On doing this, it launches a dialog with three to four options, like the ES File Explorer: Files, Gallery, etc.
I wish to read this as a Bitmap using BitmapFactory.decodeFile(path).
When I choose the file from ES File Explorer, the path is computed like this:
String path = imageData.getData().getPath();
However, this does not work for choosing an image from the Gallery or Files, where the value of path is /external/images/media/38 for example.
So I put a check, to convert such a path that begins with external using:
if (path.startsWith("/external")) {
path = getFilePathFromUri(imageData.getData());
}
private String getFilePathFromUri(Uri uri) {
Cursor cursor = managedQuery(uri,
new String[] { MediaStore.Images.Media.DATA },
null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
This converts the above path to /mnt/sdcard/dcim/Camera/2011-11-13_15-33-24_304.jpg and now this works for BitmapFactory.decodeFile(path).
I understand that hardcoding such a check (/external) is not a good idea, is there a better, perhaps generic way to resolve the format of the URI returned in the Intent?
I understand that hardcoding such a check (/external) is not a good idea, is there a better, perhaps generic way to resolve the format of the URI returned in the intent?
imageData.getData() returns a Uri. If its path starts with file:// (this is what most file managers will return), then the uri represents a file. Another option is uri represents a content item (this is what Gallery will return), in this case its path starts with content:// and you should call ContentProvider for an actual image file path (as you do in your sample). So I think you could go with something like this:
Uri uri = imageData.getData();
String scheme = uri.getScheme();
if ("file".equals(scheme)) {
// process as a uri that points to a file
} else if ("content".equals(scheme)) {
// process as a uri that points to a content item
}
You can ask MediaStore to create your Bitmap from the Uri:
Bitmap bmp = MediaStore.Images.Media.getBitmap( getContext().getContentResolver(), uri );
Related
I'm trying to get a video from the Android Gallery and get the full path, so I can upload the video in an app...
I'm displaying the gallery like this:
Intent intent = new Intent();
intent.setType("video/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
activity.startActivityForResult(Intent.createChooser(intent, context.getResources().getString(R.string.popup_menu_share_video_title)), Const.PICK_VIDEO_REQUEST);
I then extract the uri:
Uri selectedUri = data.getData();
String selectedPath = getRealPathFromURI(this, selectedUri);
And attempt to get the path:
public String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = { MediaStore.Video.Media.DATA };
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
But I can't seem the get the path...
I can see all the videos if I do this:
public static void dumpVideos(Context context ) {
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Video.Media.DATA };
Cursor c = context.getContentResolver().query(uri, projection, null, null, null);
int vidsCount = 0;
if (c != null) {
vidsCount = c.getCount();
while (c.moveToNext()) {
Log.d("VIDEO", c.getString(0));
}
c.close();
}
Log.d("VIDEO", "Total count of videos: " + vidsCount);
}
So I'm thinking there must be some real simple step, that I'm missing...
I'm trying to get a video from the Android Gallery
First, there is no "Android Gallery". There are many apps that might be considered "gallery" apps. There are thousands of device models, and they will ship with a wide variety of "gallery" apps, in addition to those that the user installs.
Second, ACTION_GET_CONTENT is not somehow magically limited to "gallery" apps. Any app can elect to support ACTION_GET_CONTENT for video/*.
and get the full path
That is impossible. You have no way of knowing what app the user chooses to handle your ACTION_GET_CONTENT request. You have no way of knowing what sort of Uri you will get back. You have no way of knowing where the content identified by that Uri will be stored, as it does not have to be a file on the filesystem that you can access.
so I can upload the video in an app
Use openInputStream() on ContentResolver to get an InputStream on the content identified by the Uri. Then, either:
Use that stream directly with your preferred HTTP client API to upload the content, or
Use that stream to make a local file copy of the content, then use that file with your preferred HTTP client
And attempt to get the path:
At best, that code will work in very limited circumstances:
The app that the user chooses to handle your ACTION_GET_CONTENT request happens to return a content Uri from the MediaStore
That content happens to be on external storage, not removable storage
Since that will not cover all of your scenarios, get rid of that code.
I can see all the videos if I do this
No, you can get paths for some videos:
Not every video accessible via ACTION_GET_CONTENT will be known to the MediaStore
Not every video in the MediaStore will be on external storage, where you might be able to access it via filesystem APIs
you can solve this issue by using ACTION_PICK instead of ACTION_GET_CONTENT. I believe unless you need the functionality of adding pictures/videos from Dropbox etc. it's not worth it.If all you need is to get stuff from the gallery use this code:
if (Build.VERSION.SDK_INT < 19) {
final Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/* video/*");
startActivityForResult(photoPickerIntent, PICK_PHOTO_ACTIVITY_REQUEST_CODE);
} else {
final Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("*/*");
photoPickerIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
startActivityForResult(photoPickerIntent, PICK_PHOTO_ACTIVITY_REQUEST_CODE);
}
I just tried the code you posted with ACTION_PICK and it works perfectly!
I'm trying to get real path from Uri(Of selected image from gallery) but This function returns always null value :
//Convert the image URI to the direct file system path of the image file
public String getRealPathFromURI(Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = { MediaStore.Images.Media.DATA };
cursor = getActivity().getContentResolver().query(contentUri, proj, null, null, null);
cursor.moveToFirst();
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
That's because the image you're selecting is not physically available on the device.
Images from gallery or other choose can come from different sources that does not provide a physical file, like a cloud storage for example.
Check this code here on how to open the Uri as a InputStream that can be used to create a copy of the file (or just read directly).
Android: Getting a file URI from a content URI?
edit:
I'm doing some extra research on it, apparently it also varies on how you request this Uri.
if you request it like this (which is the current preferred method as per Android guides):
i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(i, REQUEST_STORAGE);
and it opens the KitKat style unified selector, if you choose an image from there, it will always return null. If you open the left-drawer menu, select the gallery (or a file browser), and pick an image from there, then, if it is a local file image, you will have the correct path.
on the other hand, if you request it like this (which is the old method):
i = new Intent(Intent.ACTION_PICK);
i.setType("image/*");
startActivityForResult(i, REQUEST_STORAGE);
it will directly open the Gallery, and again, if it is a local file image, you will have the correct path.
So yeah, I still don't believe there's a way to force local only, but at least you have more information to make an informed decision on your coding.
I am using the DownloadManager to download an image to the system's gallery and then in the Broadcast receiver (once the download succeeds) using an Intent to set the image as the wallpaper.
Everything was working fine but then recently on 4.4 I started to get an exception in the Photos/Google+ app because it is expecting a content URI and not a file URI.
So my question is if anyone knows how to convert a full file path/URI (file://) into a content style URI (content://)?
Sorry for the lack of source code, I am away from the computer that has the source, but I hope the question makes sense without it, get a content style uri from a full path.
EDIT:
The image is copied into the system's gallery or media gallery, not saved within my apps internal storeage.
Here is an example of what I want to convert:
file:///storage/emulated/0/Pictures/Rockstar/image.jpg
to
content://media/internal/images/media/445
EDIT 2:
Here is the error that I get from the Google+ app:
04-21 10:50:35.090: E/AndroidRuntime(7220): FATAL EXCEPTION: main
04-21 10:50:35.090: E/AndroidRuntime(7220): Process: com.google.android.apps.plus, PID: 7220
04-21 10:50:35.090: E/AndroidRuntime(7220): java.lang.RuntimeException: Unable to resume activity
{com.google.android.apps.plus/com.google.android.apps.photos.phone.SetWallpaperActivity}:
java.lang.IllegalArgumentException: Image URI must be of the content scheme type
Here is the code that I use to let the user set the wallpaper:
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Uri u = Uri.parse(uriString);
Intent wall_intent = new Intent(Intent.ACTION_ATTACH_DATA);
wall_intent.setDataAndType(u, "image/*");
wall_intent.putExtra("mimeType", "image/*");
Intent chooserIntent = Intent.createChooser(wall_intent,
"Set As");
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(chooserIntent);
}
Where uriString is:
file:///storage/emulated/0/Pictures/Rockstar/image.jpg
I was able to figure it out. It was a combination of the code found here: Converting android image URI and scanning the media file after downloading.
So after the file finished downloading I get the path and do the following:
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
//Update the System
Uri u = Uri.parse(uriString);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, u));
//Get the abs path using a file, this is important
File wallpaper_file = new File(u.getPath());
Uri contentURI = getImageContentUri(context, wallpaper_file.getAbsolutePath());
For some reason starting the media scanner, newing the file, and getting the absolute path are important, I'm not exactly sure why but I can't spend any more time on this!
The way to convert from a file URI to a content URI is as follows (taken from the linked StackOver flow post:
public static Uri getImageContentUri(Context context, String absPath) {
Log.v(TAG, "getImageContentUri: " + absPath);
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
, new String[] { MediaStore.Images.Media._ID }
, MediaStore.Images.Media.DATA + "=? "
, new String[] { absPath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI , Integer.toString(id));
} else if (!absPath.isEmpty()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, absPath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
Maybe this will help someone in the future.
So my question is if anyone knows how to convert a full file path/URI (file://) into a content style URI (content://)?
Implement a ContentProvider. FileProvider offers an out-of-the-box solution for serving up local files.
I'm not sure about the technique you are using to set the wallpaper but the easiest way is probably to use WallpaperManager.setStream() which doesn't require any URI.
Also note that a file URI only works between apps if the file is publicly accessible so a content URI is a more general solution.
Using a content URI implies that a ContentProvider will serve the file. Which one depends on where your file is located.
If your app has a direct read access to the file, you can implement a content provider in your app by using for example the FileProvider class of the support library, but this should really only be used if the file is located in the private data storage of your app.
If the image is added to the system media gallery, you should probably use the URI provided by the MediaStore.
I have an application in which I can use the device's camera to take a picture. What I would like to do is to start the ACTION_IMAGE_CAPTURE intent without assigning an EXTRA_OUTPUT, and then move the file that is created in the default location to my own custom location using file.renameTo. My code is something like this:
/* Start camera activity without EXTRA_OUTPUT */
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, _REQUESTCODE_ATTACH_CAMERA);
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch(requestCode) {
case _REQUESTCODE_ATTACH_CAMERA:
/* Get path to most recently added image */
final String[] imageColumns = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA };
final String imageOrderBy = MediaStore.Images.Media._ID + " DESC";
Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageColumns, null, null, imageOrderBy);
String fullPath = "";
if(imageCursor.moveToFirst()){
fullPath = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
imageCursor.close();
}
File f = Environment.getExternalStorageDirectory();
f = new File(f.getAbsolutePath() + File.separator + "DCIM" + File.separator + MY_APP_NAME;
if(!f.exists()) {
f.mkdirs();
}
/* Create new file based on name of most recently created image */
File oldFile = new File(fullPath);
String newPath = f.getAbsolutePath() + File.separator + oldFile.getName() ;
/* Move file with renameTo */
oldFile.renameTo(new File(newPath));
break;
...
}
}
}
All of this works quite well, however there is one strange thing that is occurring. In my app, I have another button that allows selecting an existing image from the phone's gallery. That code looks like this:
Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
activity.startActivityForResult(galleryIntent, _REQUESTCODE_ATTACH_GALLERY);
This also works, but if I take a picture with the camera using the code posted above, and then try to select another image from the gallery, there will be blank "broken link" type items in the gallery that contain no content and are unselectable. These seem to correspond with photos taken and moved using renameTo; if I put in code in onActivityResult to post the filename to LogCat, the name that gets logged is the same as the name of the previously moved file that it corresponds to. Trying to create a File object or in any way access that filename, results in null objects and force closes.
The strange part is that there is no evidence of these "broken link" files in Eclipse DDMS, nor in the phone itself if I use Root Browser, and they disappear if I remount the SD Card.
The whole reason I am moving the images after capturing them with the camera is to avoid filling up the phone's gallery storage with unnecessary images. While these empty "broken link" type files don't appear to be taking up any storage space, they would still be very annoying to an end-user trying to browse through their gallery. Does anyone have any ideas on what is happening here or how to solve this problem?
EDIT:
Here is a photo showing what the gallery looks like with a "broken link" type image displayed. One of these will appear for every photo that is taken using my app, and they will all disappear if I remount the SD Card.
Thanks in part to this SO thread, I have discovered a solution. It actually makes sense that it would behave this way since there is a table kept for media content and so removing something without telling the table would definitely create a "broken link" type scenario.
The ultimate solution is to use contentResolver.delete to remove the reference to the file within the content resolver, but there are two different ways that I have found that will work.
/* Moving with renameTo */
//Use the same exact code as I had before (shortened for brevity) to move the file
oldFile.renameTo(newFile);
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
Getting the URI in this way is necessary because it requires a reference to the image in the contentResolver rather than the path to its location in storage. This way might feel dirty to some since you are moving a file and then calling a delete function on that file in order to sort of trick the content resolver into removing the link to the file. If you would rather, you can do it without using renameTo so that the call to delete(...) actually does delete the image.
/* Moving with streams */
//Get streams
InputStream in = new FileInputStream(oldFile);
OutputStream out = new FileOutputStream(newFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
//Read old file into new file
while((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
The call to contentResolver.delete is the same either way, I just wanted to point out that it will still work if the image has already been removed.
During this I discovered a solution to a problem that I didn't even realize that I had that I will post here as well in case anyone with this same problem comes across this in the future. In order to keep the image as selectable in the device gallery from the new location, you need to let the media scanner know that a change has been made. There are two ways that I found to do this:
/* This is the only way that I know of to handle multiple new files at once. I
really would use this sparingly, however, since it will rescan the entire
SD Card. Not only could this take a long time if the user has a lot of files
on their card, it will also show a notification so it is not exactly a
transparent operation. */
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
/* You *could* do multiple files with this by passing in the path for each one
in the array of Strings, however an instance of this will get called for each
one rather than it doing them all at once. Likewise, your onScanCompleted
(if you choose to include one) will get called once for each file in the list.
So really, while this is much better for a small number of files, if you plan
on scanning a very large amount then the full rescan above would probably be
a better option. */
MediaScannerConnection.scanFile(context, new String[]{ newFilePathAsString }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
//This executes when scanning is completed
}
}
);
(I have read a lot of similar questions, but bear with me here)
I need to send an image from one Activity (custom camera acitvity), where the second Activity is to upload the image to Picasa Web Album via Google API.
Every example I've found goes something like this:
File f = new File(cacheDir, "image_name.jpg");
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
startActivity(intent);
This works perfectly fine when I use the standard Android Picasa upload activity (or any other sharing app). I can also upload photos via the Picasa example app I am using, when sharing the image from gallery/camera etc.
But I cannot figure out how to build an Intent which uses a "content://---" uri and pass this to another application (neither for this example app or the Picasa standard app)...
Specificially: How can I create an Intent which is compatible with the code below (i.e. uses "content://" uri instead of "file://" uri)?
static class SendData {
String fileName;
Uri uri;
String contentType;
long contentLength;
SendData(Intent intent, ContentResolver contentResolver) {
Bundle extras = intent.getExtras();
if (extras.containsKey(Intent.EXTRA_STREAM)) {
Uri uri = this.uri = (Uri) extras.get(Intent.EXTRA_STREAM);
String scheme = uri.getScheme();
if (scheme.equals("content")) {
Cursor cursor = contentResolver.query(uri, null, null, null, null);
cursor.moveToFirst();
this.fileName = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME));
this.contentType = intent.getType();
this.contentLength = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE));
}
}
}
}
Retrieving the File-information from a File uri manually leads to NullPointerException with the Google Http Request used in the app.
Hardcoding the Content uri works. E.g:
uploadIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://media/external/images/media/11"));
Information related to Media files is stored by the MediaStore, which is a ContentProvider (http://developer.android.com/reference/android/provider/MediaStore.Images.html)
MediaStore.Images.DATA column corresponds to the file path and MediaStore.Images._ID column corresponds to the ID.
You need to query for the ID corresponding to your file path and then create a ContentUri out of it (which will be MediaStore.Images.Media.EXTERNAL_CONTENT_URI + id if the image is on the external storage, I'll try to think of a better way to translate the ID into a Content Uri).
http://developer.android.com/reference/android/provider/MediaStore.Images.Media.html#query(android.content.ContentResolver, android.net.Uri, java.lang.String[]