My app allows a user to save an image to their SD card. But I'm not sure how to make it appear in the gallery until you unmount and remount the SD card. I have googled for a couple of days with this problem but am not sure how to make it appear automatically. I found
this link but I'm not sure how to use the class. This is what i use to save the file. At the bottom of the try catch block is where I want to scan the sd card for new media.
FileOutputStream outStream = null;
File file = new File(dirPath, fileName);
try {
outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch {
...
}
If anyone could point me in the right direction, I would appreciate.
I've tried plenty of different methods to trigger the MediaScanner, and these are my results.
SendBroadcast
The most simple and naive solution. It consists in executing the following instruction from your code:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
However, this no longer works in KitKat devices, due to a lack of required permissions.
MediaScannerWrapper
As posted here (per #Brian's answer), it consists in wrapping a MediaScannerConnection instance in order to trigger the scan() method over a specific directory. This method has proven to be working fine for 4.3 and below, but still no luck with KitKat (4.4+).
FileWalker
One of the many Play Store apps that tries to overcome the MediaStore's lack of commitment to update its database is ReScan SD. It sends a lot of different broadcasts:
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file://" + Environment.getExternalStorageDirectory())));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable/SD")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable/MicroSD")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///mnt/Removable/MicroSD")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///mnt")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///storage")));
sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable")));
and tries to support KitKat by manually triggering the scan() method over each file of the base directory. Unfortunately, this is both very CPU-intensive and time-consuming, so it is not very recommended.
"The shell way"
The only thing that seem to work with KitKat in some cases is sending the broadcast via adb shell. So, this snippet allows you to do just that programmatically:
Runtime.getRuntime().exec("am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://" + Environment.getExternalStorageDirectory());
It is more of an hack-ish way of doing it, but at the moment is the best I could come up with.
Bottom line
Each of the above solutions actually works for everything that is not KitKat. That's because, thanks to Justin, a bug has been found and issued to the official Tracker. This means that, until the bug is ironed out, we are left with no true KitKat support.
Which one to use? Among those, I would use the MediaScannerWrapper solution, together with the shell-ish approach (the last one).
Since the last answer I posted apparently wasn't an appropriate method, I found another method here. You basically create a wrapper class, initialize it, and then call the scan() method. Very helpful post. Let me know if this isn't appropriate either.
Use MediaScannerConnection:
http://developer.android.com/reference/android/media/MediaScannerConnection.html
It can be a little bit of a pain because of the multiple levels of asynchronous calls, so as of API 8 (Froyo) there is a helper function:
http://developer.android.com/reference/android/media/MediaScannerConnection.html#scanFile(android.content.Context, java.lang.String[], java.lang.String[], android.media.MediaScannerConnection.OnScanCompletedListener)
You could also call media scanner explicitly by sending broadcast.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri
.parse("file://"
+ Environment.getExternalStorageDirectory())));
Edit
This was an old post. Updating it to new versions
Android is taking steps to prevent apps from spoofing more system broadcasts like this.
If you want to tell Android to index a file you put on external storage, either use MediaScannerConnection or ACTION_MEDIA_SCANNER_SCAN_FILE
Reference: This post
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
final Uri contentUri = Uri.fromFile(outputFile);
scanIntent.setData(contentUri);
sendBroadcast(scanIntent);
} else {
final Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory()));
sendBroadcast(intent);
}
If the above piece of code is not working you can try the following:
MediaScannerConnection.scanFile(this, new String[] {
file.getAbsolutePath()
}, null, new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
}
});
Here is another way to force scan:
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,"uri to file"));
And then system will fire ACTION_MEDIA_SCANNER_FINISHED broadcast so you can react on it with BroadcastReceiver
In order to be able to receive ACTION_MEDIA_SCANNER_FINISHED broadcast, intent filter should contain data scheme:
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
context.registerReceiver(mMediaScannerFinishReceiver, intentFilter);
from the doc:
public static final String ACTION_MEDIA_SCANNER_SCAN_FILE
Added in API level 1 Broadcast Action: Request the media scanner to
scan a file and add it to the media database. The path to the file is
contained in the Intent.mData field.
and
public static final String ACTION_MEDIA_SCANNER_FINISHED
Added in API level 1 Broadcast Action: The media scanner has finished
scanning a directory. The path to the scanned directory is contained
in the Intent.mData field.
You can use MediaStore.Images.Media.insertImage to insert an item into the gallery.
Related
My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Because I want to make sure the MediaStore has the latest information without having to reboot I'd like to trigger the MediaScanner using the popular way I found on SO
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + Environment.getExternalStorageDirectory())));
This works fine on my Samsung S2 w/ICS but not on my Nexus 7 w/JellyBean. Logcat shows this on my Nexus 7:
WARN/ActivityManager(480): Permission denied: checkComponentPermission() owningUid=10014
WARN/BroadcastQueue(480): Permission Denial: broadcasting Intent { act=android.intent.action.MEDIA_MOUNTED dat=file:///storage/emulated/0 flg=0x10 } from com.example.foo.bar (pid=17488, uid=10046) is not exported from uid 10014 due to receiver com.android.providers.downloads/.DownloadReceiver
INFO/ActivityManager(480): Start proc com.google.android.music:main for broadcast com.google.android.music/.store.MediaStoreImportService$Receiver: pid=17858 uid=10038 gids={50038, 3003, 1015, 1028}
INFO/MusicStore(17858): Database version: 50
INFO/MediaStoreImporter(17858): Update: incremental Added music: 0 Updated music: 0 Deleted music: 0 Created playlists: 0 Updated playlists: 0 Deleted playlists: 0 Inserted playlist items: 0 Deleted playlist items: 0 Removed orphaned playlist items: 0
The last line sounds encouraging in theory, but the values are always 0 even after new files had been pushed to the SD card (via adb push). On my older device (S2) it does remount the SD card.
I've added the following permissions to my AndroidManifest.xml but it behaves the same as without those permissions:
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Any ideas/alternatives?
Edit 1:
Note that I don't know any file paths of new or modified or deleted files. I just want to make sure the MediaStore is up-to-date.
Here's the sample code based on CommonsWare's answer:
MediaScannerConnection.scanFile(activity, new String[]{path}, null,
new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(final String path, final Uri uri) {
Log.i(TAG, String.format("Scanned path %s -> URI = %s", path, uri.toString()));
}
});
Even though in most of the cases, where one knows the files to be added/updated/etc. to the MediaStore, one should follow CommonsWare's answer, I wanted to post the my solution where I need to do it the rough way because I don't know the file paths. I use this mostly for testing/demoing:
Uri uri = Uri.fromFile(Environment.getExternalStorageDirectory());
activity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, uri));
BTW, no permissions are necessary for either solution.
using the popular way I found on SO
Faking ACTION_MEDIA_MOUNTED broadcasts has never been an appropriate solution IMHO.
Any ideas/alternatives?
Use MediaScannerConnection, such as via its scanFile() static method.
My answer is a little late, but it might help those, who save a new file, and would like to extend the media store by just that file on Android Kitkat: On Android Kitkat the intent ACTION_MEDIA_MOUNTED is blocked for non-system apps (I think, because scanning the whole filesystem is pretty expensive). But it is still possible to use the intent ACTION_MEDIA_SCANNER_SCAN_FILE to add a file to the media store:
File f = new File(path to the file you would like to add to the media store ...);
try {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(f);
mediaScanIntent.setData(uri);
sendBroadcast(mediaScanIntent);
} catch(Exception e) {
...
}
I'm making a file manager in which picture items have a small thumbnail.
I get thumbnail image by using MediaStore. Everything works fine. But when I rename or move a file, the thumbnail does not show up.
I've found a piece of code to refresh MediaStore:
getActivity().sendBroadcast(
new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + Environment.getExternalStorageDirectory())));
It worked but I must wait 4 or 5 second and refresh, then the thumbnail updates.
How to get thumbnail of image immediately after rename or moving?
What happen if you use ACTION_MEDIA_SCANNER_SCAN_FILE instead of ACTION_MEDIA_MOUNTED, (i.e. trigger a refresh for a single file instead of for the complete directory hierarchy) ?
You will need to replace the URI of the directory with the URI of the file, obtained for example using Uri.fromFile().
When you move or rename a file you should refresh the old and the new URIs.
The recommended way to update one specific image in Android is using ACTION_MEDIA_SCANNER_SCAN_FILE intent. And for smoother
You can check it at Basic Photo Handling Training in Android Developer Site.
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
If you want to show new thumbnail immediately for some missing files, you can do it by yourself. First, check the MediaStore as before, and if the returned thumbnail is null then generate your own one using ThumbnailUtils or BitmapFactory.
And, For handling a bitmap and displaying it, there is a quiet straightforward sample in Android Training Course.
Have you tried doing the scan directly on the directory you are changing? So instead of
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
something like
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory() + "/path/to/your/folder")));
An alternative would be to manually use ThumbnailUtils.
Actually sending Intent.ACTION_MEDIA_MOUNTED broadcast intent is really ugly. Read this post http://androidyue.github.io/blog/2014/01/19/scan-media-files-in-android As to renaming files. You should remove the older file from the library and then add the new one into the library. I think this could help you.
Because I want to make sure the MediaStore has the latest information without having to reboot I'd like to trigger the MediaScanner using the popular way I found on SO
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + Environment.getExternalStorageDirectory())));
This works fine on my Samsung S2 w/ICS but not on my Nexus 7 w/JellyBean. Logcat shows this on my Nexus 7:
WARN/ActivityManager(480): Permission denied: checkComponentPermission() owningUid=10014
WARN/BroadcastQueue(480): Permission Denial: broadcasting Intent { act=android.intent.action.MEDIA_MOUNTED dat=file:///storage/emulated/0 flg=0x10 } from com.example.foo.bar (pid=17488, uid=10046) is not exported from uid 10014 due to receiver com.android.providers.downloads/.DownloadReceiver
INFO/ActivityManager(480): Start proc com.google.android.music:main for broadcast com.google.android.music/.store.MediaStoreImportService$Receiver: pid=17858 uid=10038 gids={50038, 3003, 1015, 1028}
INFO/MusicStore(17858): Database version: 50
INFO/MediaStoreImporter(17858): Update: incremental Added music: 0 Updated music: 0 Deleted music: 0 Created playlists: 0 Updated playlists: 0 Deleted playlists: 0 Inserted playlist items: 0 Deleted playlist items: 0 Removed orphaned playlist items: 0
The last line sounds encouraging in theory, but the values are always 0 even after new files had been pushed to the SD card (via adb push). On my older device (S2) it does remount the SD card.
I've added the following permissions to my AndroidManifest.xml but it behaves the same as without those permissions:
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Any ideas/alternatives?
Edit 1:
Note that I don't know any file paths of new or modified or deleted files. I just want to make sure the MediaStore is up-to-date.
Here's the sample code based on CommonsWare's answer:
MediaScannerConnection.scanFile(activity, new String[]{path}, null,
new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(final String path, final Uri uri) {
Log.i(TAG, String.format("Scanned path %s -> URI = %s", path, uri.toString()));
}
});
Even though in most of the cases, where one knows the files to be added/updated/etc. to the MediaStore, one should follow CommonsWare's answer, I wanted to post the my solution where I need to do it the rough way because I don't know the file paths. I use this mostly for testing/demoing:
Uri uri = Uri.fromFile(Environment.getExternalStorageDirectory());
activity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, uri));
BTW, no permissions are necessary for either solution.
using the popular way I found on SO
Faking ACTION_MEDIA_MOUNTED broadcasts has never been an appropriate solution IMHO.
Any ideas/alternatives?
Use MediaScannerConnection, such as via its scanFile() static method.
My answer is a little late, but it might help those, who save a new file, and would like to extend the media store by just that file on Android Kitkat: On Android Kitkat the intent ACTION_MEDIA_MOUNTED is blocked for non-system apps (I think, because scanning the whole filesystem is pretty expensive). But it is still possible to use the intent ACTION_MEDIA_SCANNER_SCAN_FILE to add a file to the media store:
File f = new File(path to the file you would like to add to the media store ...);
try {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(f);
mediaScanIntent.setData(uri);
sendBroadcast(mediaScanIntent);
} catch(Exception e) {
...
}
I have an application that makes changes to some of the files in the media folders (DCIM/Camera specifically)
After I make theses changes the application sends this broadcast in order to force the MediaScanner to run so that my changes get reflected in the Gallery app the next time it is opened.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
This works perfect on devices running stock android. However on devices with motoblur it fails and gives me this output in the log:
Permission Denial: broadcasting Intent { act=android.intent.action.MEDIA_MOUNTED dat=file:///mnt/sdcard } from com.my.package (pid=20882, uid=10109) requires com.motorola.blur.service.blur.Permissions.INTERACT_BLUR_SERVICE due to registered receiver BroadcastFilter{40a444c8 ReceiverList{40a22888 13696 com.motorola.blur.service.blur/10023 remote:40a340b8}}
Is there some way I can use this INTERACT_BLUR_SERVICE permission? Or is there some other way I can get the Media Scanner to run on command?
Souldn't adding this permission in your AndroidManifest solve the problem?... like this :
<uses-permission android:name=
"com.motorola.blur.service.blur.Permissions.INTERACT_BLUR_SERVICE"/>
I never found a way to trigger the media scanner. But I was pointed towards a different means of deleting the images and videos, rather than just deleting the files on the SD card, I now use a ContentResolver to delete the media.
Here is a snippet of how I've done it:
//Uri imgUri = Uri.parse("content://android.provider.MediaStore.Images.Media");
ContentResolver cr = getContentResolver();
int count = cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null);
count += cr.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null);
Log.i(myTag, "Deleted " + count + " files from media database");