I have an app which performs processing on an image selected by the user using from the gallery, after processing the modified image is then saved back to the original image's folder with a modified filename. On specific devices (mainly Samsungs) the new KitKat permission limitations severely limit where a non system app can write to e.g. external SD card or back to typical gallery locations.
The documentation advises to write to the private package specific folder obtained via
getExternalFilesDir(null)
This is fine and the image is saved correctly but then a subsequent call to
MediaScannerConnection.scanFile
fails to update the central media database correctly as the modified image doesn't appear in the gallery, even a reboot of the device which should force a full scan of all media doesn't trigger the image to be displayed.
In other words the following code doesn't work:
File appDir = MyActivity.this.getExternalFilesDir(null);
File file = new File(appDir, "new_image.jpeg");
// Process image and write file away...
MediaScannerConnection.scanFile(this, new String[] { file.getAbsolutePath() }, null, new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
Log.i(LOG_TAG, String.format("Scanned file: %s", path));
}
});
The onScanCompleted method fires but the gallery doesn't show the new image.
Has anyone seen this behaviour, does MediaScanner not scan 'private' app folders and if so how else do I get the gallery to detect and display the new image.
If you want images to be picked up by the gallery, you should put them in the proper public location. You can get the proper location using Environment.getExternalStoragePublicDirectory().
Related
I have a problem when receiving a file from an external app. I've been following the tutorial on https://developer.android.com/training/sharing/receive.html and it works fine until I want to handle the image in my code.
The problem I have is if I select and send only one file to my app, I am not able to read it after converting it from URI to a FILE object. However, if I send two or more images (the very same image selected before plus an additional one from the same directory), then I actually can read the files (all of them).
Why is that? Even setting the file to setReadable(true); I can not read it afterwards.
Target SDK is 23 and yes, I already implemented the request for permission in the code that is needed from API 23+. So this can't be the problem.
I need to be able to read the received files no matter if it was only one or a list of multiple.
On a side note: if I send any amount of images from the Google Photos app (one or multiple), I never can read the file. Images sent from the "ES File Explorer" app are readable in the code but not readable if I only send one single file to my app.
Here is my code snippet of the problematic part:
// THIS PART WORKS. RECEIVING MULTIPLE FILES ARE READABLE IN THE CODE BELOW.
void handleSendMultipleImages(Intent intent) {
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (imageUris != null) {
addImagesToNewOrExistingContact(imageUris);
}
}
// THIS PART DOES NOT WORK. I CAN NOT READ THE FILE IN THE CODE BELOW.
private void addImageToNewOrExistingContactDialog(Uri imageUri) {
ArrayList imageUris = new ArrayList<>();
imageUris.add(imageUri);
addImagesToNewOrExistingContact(imageUris);
}
private void addImagesToNewOrExistingContact(final ArrayList<Uri> imageUris) {
for (Uri uri : imageUris) {
File f = new File(uri.getPath());
f.setReadable(true);
f.setWritable(true);
boolean readd = f.canRead(); // FALSE, but why?
boolean exec = f.canExecute(); // FALSE, but why?
}
}
Files I tested this with:
Selected two files from "ES File Explorer" and sent them to my app:
file:///storage/emulated/0/Pictures/Adobe%C2%AE%20Photoshop%C2%AE%20Touch/1452348875289.jpg
file:///storage/emulated/0/Pictures/Adobe%C2%AE%20Photoshop%C2%AE%20Touch/1455733673513.jpg
Both canRead() = TRUE
Selected one file from "ES File Explorer" and sent it to my app:
content://media/external/images/media/33675
canRead() = FALSE
Actually the files content://media/external/images/media/33675 and file:///storage/emulated/0/Pictures/Adobe%C2%AE%20Photoshop%C2%AE%20Touch/1455733673513.jpg are the exact same files.
Selected two files from "Google Photos" and send them to my app:
content://com.google.android.apps.photos.contentprovider/0/1/shared%3A%2Flocal%253A4541959b-3222-4ee0-b838-67049141b864%2FV2xDV01jNWhWVDRCQXRMY202YTh3NFNES1N4M01R/REQUIRE_ORIGINAL/NONE/1290260075
content://com.google.android.apps.photos.contentprovider/0/1/shared%3A%2Flocal%253A20e65034-795f-4300-9472-64a598afc4c1%2FV2xDV01jNWhWVDRCQXRMY202YTh3NFNES1N4M01R/REQUIRE_ORIGINAL/NONE/1096770166
Both canRead() = FALSE
Thanks for any help in advance.
Use openInputStream(uri) on getContentResolver(). No need for a File class.
I am having a problem with selecting image file from external storage using file picker in Android. This question is the consequence of this question - No such file or diectory error in image file upload using Retrofit in Android. What my problem is opening and reading file from external storage on activity result. I want to convert result URI into File.
I read a pdf file from download folder on activity result
Uri bookUri = data.getData();
if(bookUri!=null)
{
String filePath = bookUri.toString();//bookUri.toString()
String mime = app.getMimeType(filePath);
if(mime!=null && !mime.isEmpty() && (mime.toLowerCase()=="application/pdf" || mime.toLowerCase()=="application/txt" || mime.toLowerCase()=="application/text"))
{
bookFile = new File(bookUri.getPath());
ivBookFile.setImageResource(R.drawable.book_selected);
}
else{
Toast.makeText(getBaseContext(),"Unable to process file you have chosen.",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(bookUri.getPath()); to convert into File. The above code works well. It is working. The problem is now I am trying to open an image file in DCIM/Camera folder on activity result.
This is the code I used
Uri selectedImageUri = data.getData();
if(selectedImageUri!=null)
{
try{
bmpCoverImage = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImageUri);
imageFile = new File(selectedImageUri.getPath());
if(bmpCoverImage!=null)
{
ivCoverImage.setImageBitmap(bmpCoverImage);
}
}
catch (IOException e)
{
Toast.makeText(getBaseContext(),"An error occurred with the file selected",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(selectedImageUri.getPath()); like I did in reading pdf file. This time the code is not working. When I do operation with the file like in previous question, it gives me error.
I used this way also
imageFile = new File(Environment.getExternalStorageDirectory(),selectedImageUri.getPath());
I got the same error. How can I open the image file correctly from external storage? How can I convert the chosen file URI from external storage into File?
I am having a problem with selecting image file from external storage using file picker in Android
If you are referring to the code that you are using in this question, you are not "using file picker". You are using ACTION_GET_CONTENT, which has never been a "file picker", nor will it ever be a "file picker".
I want to convert result URI into File.
Usually, that is not necessary. But, if that is what you want to do:
use ContentResolver and openInputStream() to get an InputStream on the content represented by the Uri
create a FileOutputStream on your desired file
use Java I/O to copy the bytes from the InputStream into the FileOutputStream
The above code works well. It is working.
It works for the small number of devices that you tested, for the specific activities that the user chose to handle the ACTION_GET_CONTENT request. It will not work on most Android devices, and it will not work in most circumstances. The only time that code will work is if the Uri has a file scheme. Most of the time, it will not. Instead, it will have a content scheme, representing content supplied by a ContentProvider.
Please how can I open the image file correctly from external storage?
If you wish to continue using ACTION_GET_CONTENT, please understand that this has nothing to do with external storage specifically. You are not getting a file, on external storage or elsewhere. You are getting a Uri. This is akin to a URL, such as the URL for this Web page. Just as a URL does not necessarily point to a file on your hard drive, a Uri does not necessarily point to a file on the filesystem. Use a ContentResolver and DocumentFile to work with the Uri and the content that it identifies.
If you want to always get files on external storage (and nowhere else), then use an actual file picker library.
I am using the native Android camera and save files to a application data folder (/mnt/sdcard/Pictures/). At the same time - on some devices - another copy of photo is saved to DCIM folder.
This is my code:
private void startStockCameraForResult()
{
// create Intent to take a picture and return control to the calling application
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// mediaStorageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
mNextImageFileUri = ImageFileUtils.getOutputMediaFileUri();
intent.putExtra(MediaStore.EXTRA_OUTPUT, mNextImageFileUri); // set the image file name
// start the image capture Intent
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
How can I prevent the saved additional copy of the image in the DCIM folder?
My Problem is that code produces
1 photo : Samsung Galaxy SIII, Huawei HUAWEI P2-6011 etc.
2 photos : Samsung Galaxy SI, Htc HTC One XL etc.
Other threads describe deleting last added image to DCIM. Problems here are devices which have no problem like Galaxy SIII and Imagename on DCIM and on application data folder is different.
Many Thanks
AFAIK, you can't reliably tell the camera apps (device-independently) a) where to save the image AND b) also to save it only once. I had to resort to this approach:
1) Just let the camera app save the picture to wherever it likes, by removing the putExtra(...) statement:
`intent.putExtra(MediaStore.EXTRA_OUTPUT, mNextImageFileUri); // set the image file name`
This (i.e. not setting EXTRA_OUTPUT) will guarantee that only one image will be saved, to the default picture location.
2) Find the last photo taken, and save its ID, for a later safety check. (Query for the last image ID, sorting by DATE_TAKEN.)
3) Fire the capture intent, and in your onActivityResult, again, get the last image taken, and save its ID, URI and path.
4) If your new pic ID is > than the one previously saved, go ahead, otherwise panic out...
5) Move the original image file (using its path) to your preferred location. Now, the original file is removed.
6) Delete the original media image entry, using its URI. Now the image is removed from the gallery, too.
7) If you also want to remove the thumbnails, well, you'll need to query and delete them similarly, but I'd advise against it: a device reboot or another media scan should refresh the thumbnail cache. Also, you may actually quite likely need that thumbnail for a short while after deleting the original image. (If you need it longer, you need be careful: if you moved the photos to the private app dir (getExternalFilesDir(Environment.DIRECTORY_PICTURES)) the media manager will not (be able to) generate thumbnails for you, so you may need to manage your own thumbnails.)
Here is my code:
File storageFile = new File("/mnt/extSdCard/DCIM/Camera/IMG_123456789.jpg");
if(storageFile.exists()) {
//copy the file to another folder
MyCopyFoo(storageFile);
if(storageFile.delete()) {
Log.d("Debug", "Success!");//have shown
//refresh sth
}
}
After operation, I checked the system gallery, and there is still a thumbnail in it.
When I restarted the system, it was gone.
I know there is some other way to handle this- the "setting"=>clear sth
What if I wanna deal with it in the code above?
Use MediaScannerConnection to notify the system of modified media files and to regenerate their metadata and thumbnails.
Mediascanner runs as part of the boot sequence so that's why a reboot also fixes the issue.
I have a custom camera that has a public method for getting the thumbnail of that last image saved to a specific folder on the sdcard...
that method looks like this:
public void getGalleryThumb(){
// TODO add Logic for gallery images..
File sdDir = new File("/sdcard/LC/images");
File[] sdDirFiles = sdDir.listFiles();
if(sdDir.length()>0){
File lastPhoto = sdDirFiles[0];
Bitmap myBitmap = BitmapFactory.decodeFile(lastPhoto.getAbsolutePath());
//SET MY IMAGE VIEW BITMAP TO LAST FILE IN sdDIRFiles
photo.setImageBitmap(myBitmap);
btn_gallery.setVisibility(View.VISIBLE);
}
//Toast.makeText(getBaseContext(), "num images in gal:"+sdDirFiles.length +"last image name: "+sdDirFiles[0], Toast.LENGTH_LONG).show();
}
i have noticed that if i delete a photo from that folder the method above does not always retrieve the right image.. I have used:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://"+ Environment.getExternalStorageDirectory()+"/LC/images/")));
upon deleting and writing new files to that sdcard/folder but it doesn't seem to do the trick plus it forces this annoying toast message about the sdcard being mounted..
any help would be appreciated
I was writing this in a comment, but I'll put the detail here. Multiple issues:
listFiles doesn't not guarantee order. You'll need to sort your files by last modified.
You shouldn't access "/sdcard" directly. Your second snippet of code has Environment.getExternalStorageDirectory(). Use that.
You get messages about the SDCARD? Where are they coming from? If you mount it to your local machine, you won't be able to access it from your app while its mounted.