On my application I write a file to the internal storage as covered on android developer. Then later on I wish to email the file I wrote into the internal storage. Here is my code and the error I am getting, any help will be appreciated.
FileOutputStream fos = openFileOutput(xmlFilename, MODE_PRIVATE);
fos.write(xml.getBytes());
fos.close();
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
...
Uri uri = Uri.fromFile(new File(xmlFilename));
intent.putExtra(android.content.Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Send eMail.."));
And the error is
file:// attachment path must point to file://mnt/sdcard. Ignoring attachment file://...
I think you may have found a bug (or at least unnecessary limitation) in the android Gmail client. I was able to work around it, but it strikes me as too implementation specific, and would need a little more work to be portable:
First CommonsWare is very much correct about needing to make the file world readable:
fos = openFileOutput(xmlFilename, MODE_WORLD_READABLE);
Next, we need to work around Gmail's insistence on the /mnt/sdcard (or implementation specific equivalent?) path:
Uri uri = Uri.fromFile(new File("/mnt/sdcard/../.."+getFilesDir()+"/"+xmlFilename));
At least on my modified Gingerbread device, this is letting me Gmail an attachment from private storage to myself, and see the contents using the preview button when I receive it. But I don't feel very "good" about having to do this to make it work, and who knows what would happen with another version of Gmail or another email client or a phone which mounts the external storage elsewhere.
I have been struggling with this issue lately and I would like to share the solution I found, using FileProvider, from the support library. its an extension of Content Provider that solve this problem well without work-around, and its not too-much work.
As explained in the link, to activate the content provider:
in your manifest, write:
<application
....
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.youdomain.yourapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
...
the meta data should indicate an xml file in res/xml folder (I named it file_paths.xml):
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="" name="document"/>
</paths>
the path is empty when you use the internal files folder, but if for more general location (we are now talking about the internal storage path) you should use other paths. the name you write will be used for the url that the content provider with give to the file.
and now, you can generate a new, world readable url simply by using:
Uri contentUri = FileProvider.getUriForFile(context, "com.yourdomain.yourapp.fileprovider", file);
on any file from a path in the res/xml/file_paths.xml metadata.
and now just use:
Intent mailIntent = new Intent(Intent.ACTION_SEND);
mailIntent.setType("message/rfc822");
mailIntent.putExtra(Intent.EXTRA_EMAIL, recipients);
mailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
mailIntent.putExtra(Intent.EXTRA_TEXT, body);
mailIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
try {
startActivity(Intent.createChooser(mailIntent, "Send email.."));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(this, R.string.Message_No_Email_Service, Toast.LENGTH_SHORT).show();
}
you don't need to give a permission, you do it automatically when you attach the url to the file.
and you don't need to make your file MODE_WORLD_READABLE, this mode is now deprecated, make it MODE_PRIVATE, the content provider creates new url for the same file which is accessible by other applications.
I should note that I only tested it on an emulator with Gmail.
Chris Stratton proposed good workaround. However it fails on a lot of devices. You should not hardcode /mnt/sdcard path. You better compute it:
String sdCard = Environment.getExternalStorageDirectory().getAbsolutePath();
Uri uri = Uri.fromFile(new File(sdCard +
new String(new char[sdCard.replaceAll("[^/]", "").length()])
.replace("\0", "/..") + getFilesDir() + "/" + xmlFilename));
Taking into account recommendations from here: http://developer.android.com/reference/android/content/Context.html#MODE_WORLD_READABLE, since API 17 we're encouraged to use ContentProviders etc.
Thanks to that guy and his post http://stephendnicholas.com/archives/974 we have a solution:
public class CachedFileProvider extends ContentProvider {
public static final String AUTHORITY = "com.yourpackage.gmailattach.provider";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
switch (uriMatcher.match(uri)) {
case 1:// If it returns 1 - then it matches the Uri defined in onCreate
String fileLocation = AppCore.context().getCacheDir() + File.separator + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
default:// Otherwise unrecognised Uri
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
#Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return 0; }
#Override public int delete(Uri uri, String s, String[] as) { return 0; }
#Override public Uri insert(Uri uri, ContentValues contentvalues) { return null; }
#Override public String getType(Uri uri) { return null; }
#Override public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) { return null; }
}
Than create file in Internal cache:
File tempDir = getContext().getCacheDir();
File tempFile = File.createTempFile("your_file", ".txt", tempDir);
fout = new FileOutputStream(tempFile);
fout.write(bytes);
fout.close();
Setup Intent:
...
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + tempFile.getName()));
And register Content provider in AndroidManifest file:
<provider android:name="CachedFileProvider" android:authorities="com.yourpackage.gmailattach.provider"></provider>
File.setReadable(true, false);
worked for me.
The error is enough specific: you should use file from external storage in order to make an attachment.
If you are going to use internal storage, try to use the exact storage path:
Uri uri = Uri.fromFile(new File(context.getFilesDir() + File.separator + xmlFilename));
or additionally keep changing the file name in the debugger and just call "new File(blah).exists()" on each of the file to see quickly what exact name is your file
it could also be an actual device implementation problem specific to your device. have you tried using other devices/emulator?
Related
I am trying to create a share button that will share an Image with some extra caption text to social media.
The code I'm using now works fine with some apps, but doesn't work in other apps.
Here's the code:
String message = "hello world";
Uri uri = Uri.parse("android.resource://" + getActivity().getPackageName() + "/" + R.raw.loremipsum2);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, message);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/jpeg");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
I've been searching for hours what the problem may be, but none of the solutions have worked for me so far.
Apps where it does work:
-Telegram
-Drive
-Reddit
Apps where it doesn't work:
-WhatsApp
-Gmail
Error when sharing to Gmail:
Gmail:Can't find android resource
java.io.FileNotFoundException: No package found for authority: android.resource://com.test.testApp/2131820547
Error when sharing to whatsapp
Access denied finding property "ro.vendor.scroll.preobtain.enable"
I'm going crazy onto this one, I need it to work and can obviously be achieved, as many apps can share images to whatsapp.
Any ideas on how to solve it? you're welcome! :)
EXTRA_STREAM is documented to take a content: Uri. That is not what you are using. Few developers know of the android.resource scheme. Fewer still will have code that handles it when an app uses one unexpectedly in EXTRA_STREAM.
For better compatibility, use a ContentProvider and a content: Uri that points to it. For example, you could share a file with FileProvider.
Okay, so finally, after researching a lot, trust me, a lot, I've finally found an answer to my question, so I'm posting it here as it seems like a pretty basic feature to have in an app.
The solution is, as CommonsWare said, using a fileProvider.
I also used a class I found on github to manage cache usage , it is linked here, as FileProvider may not use resource URIs
The idea is, store the image in cache, then call Cache class to provide a valid URI to the image in cache, and then use that URI for the EXTRA_STREAM
Follow this guide to solve the issue: (it works in all apps I've tried so far. It should also work for other File types with some little modifications.)
SOLUTION
1-Create the class that handles cache. Here's code:
public class Cache {
public static final String TAG = Cache.class.getSimpleName();
private static final String CHILD_DIR = "images";
private static final String TEMP_FILE_NAME = "img";
private static final String FILE_EXTENSION = ".png";
private Context context;
private static final int COMPRESS_QUALITY = 100;
public Cache(Context context){
this.context = context;
}
/**
* Save image to the App cache
* #param bitmap to save to the cache
* #param name file name in the cache.
* If name is null file will be named by default {#link #TEMP_FILE_NAME}
* #return file dir when file was saved
*/
public File saveImgToCache(Bitmap bitmap, #Nullable String name) {
File cachePath = null;
String fileName = TEMP_FILE_NAME;
if (!TextUtils.isEmpty(name)) {
fileName = name;
}
try {
cachePath = new File(context.getCacheDir(), CHILD_DIR);
cachePath.mkdirs();
FileOutputStream stream = new FileOutputStream(cachePath + "/" + fileName + FILE_EXTENSION);
bitmap.compress(Bitmap.CompressFormat.PNG, COMPRESS_QUALITY, stream);
stream.close();
} catch (IOException e) {
Log.e(TAG, "saveImgToCache error: " + bitmap, e);
}
return cachePath;
}
/**
* Save an image to the App cache dir and return it {#link Uri}
* #param bitmap to save to the cache
*/
public Uri saveToCacheAndGetUri(Bitmap bitmap) {
return saveToCacheAndGetUri(bitmap, null);
}
/**
* Save an image to the App cache dir and return it {#link Uri}
* #param bitmap to save to the cache
* #param name file name in the cache.
* If name is null file will be named by default {#link #TEMP_FILE_NAME}
*/
public Uri saveToCacheAndGetUri(Bitmap bitmap, #Nullable String name) {
File file = saveImgToCache(bitmap, name);
return getImageUri(file, name);
}
/**
* Get a file {#link Uri}
* #param name of the file
* #return file Uri in the App cache or null if file wasn't found
*/
#Nullable public Uri getUriByFileName(String name) {
Context context = this.context;
String fileName;
if (!TextUtils.isEmpty(name)) {
fileName = name;
} else {
return null;
}
File imagePath = new File(context.getCacheDir(), CHILD_DIR);
File newFile = new File(imagePath, fileName + FILE_EXTENSION);
return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", newFile);
}
// Get an image Uri by name without extension from a file dir
private Uri getImageUri(File fileDir, #Nullable String name) {
Context context = this.context;
String fileName = TEMP_FILE_NAME;
if (!TextUtils.isEmpty(name)) {
fileName = name;
}
File newFile = new File(fileDir, fileName + FILE_EXTENSION);
return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", newFile);
}
/**
* Get Uri type by {#link Uri}
*/
public String getContentType(Uri uri) {
return this.context.getContentResolver().getType(uri);
}
}
I've done little to none modification to this class, the author is Ivan V on 12.05.2019. (Github)
This class uses a FileProvider internally. FileProvider will allow other apps to access our app files.
To configure file providers, we have to do some modification in AndroidManifest.xml
2 - Inside the <application> tag, place the following FileProvider config:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:grantUriPermissions="true"
android:exported="false"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And create file_paths.xml in /res/xml (if the xml folder doesn't exist, create it to) with the following code:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="file_path" path="."/>
<external-files-path name="external_path" path="/" />
</paths>
3 - Create the sharing method in your activity
public void shareImage(){
Cache cache = new Cache(getActivity());
cache.saveImgToCache(BitmapFactory.decodeResource(getResources(),R.raw.loremipsum),"testImg");
Uri sendUri = cache.getUriByFileName("testImg");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "whatsAppMessage");
intent.putExtra(Intent.EXTRA_STREAM, sendUri);
intent.setType("image/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
}
Just change all image names etc and you are ready to go!!
I hope i was helpful :)
Hey so I save my pdf in external data storage. Eg:
Environment.getExternalStorageDirectory().getPath() + "/file.pdf"
Then, I try to attach it to intent to send:
File attachment = this.getFileStreamPath(fileDirectory + "/" + fileName);
Uri uri = Uri.fromFile(attachment);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setDataAndType(Uri.parse("mailto:"), "text/plain"); // I have also tried "application/pdf"
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Calc PDF Report");
emailIntent.putExtra(Intent.EXTRA_TEXT, " PDF Report");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
finish();
and im getting the error:
Caused by: java.lang.IllegalArgumentException: File /storage/emulated/0/file.pdf contains a path separator
I am thinking it is something wrong with were I am saving my file, but can't find any examples that are up-to-date.
To share a file as an email attachment using intent, you need to use a FileProvider.
/**
* Generate file content and returns uri file
*/
public static Uri generateFile(Context context) {
File pdfDirPath = new File(context.getFilesDir(), "pdfs");
pdfDirPath.mkdirs();
File file = new File(pdfDirPath, "attachment.pdf");
file.deleteOnExit();
Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".file.provider", file);
FileOutputStream os = null;
try {
Logger.info("Generate file " + file.getAbsolutePath());
os = new FileOutputStream(file);
document.writeTo(os);
document.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
return uri;
}
private void share(Context context) {
Uri uri = generateFile(context);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(EXTRA_SUBJECT, "Send something");
emailIntent.putExtra(Intent.EXTRA_TEXT, "You receive attachment");
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(emailIntent);
}
In your app add the file provider definition:
AndroidManifest.xml
<application
android:name=".DemaApplication"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.file.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
...
</application>
provider_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="internal_files"
path="/"/>
<!--<external-path name="external_files" path="./files"/>-->
</paths>
At last but not least, you need to specify the file provider path (where are your files). I hope this helps. Here, the official documentation about how to send email and attachment with intent.
Problem #1: getFileStreamPath() does not support subdirectories and is not related to external storage, where your file resides.
Problem #2: Uri.fromFile() will not work on Android 7.0, as your app will crash with a FileUriExposedException. To fix this and Problem #1, use FileProvider to set up a content Uri that you can use for EXTRA_STREAM.
Problem #3: ACTION_SEND does not use a "data" Uri (i.e., your "mailto:" should not be there).
Problem #4: The MIME type of a PDF is not text/plain — as your comment notes, use application/pdf.
Problem #5: getExternalStorageDirectory() is deprecated on Android 10 and higher, and you will not be able to write files there. Consider using getExternaFilesDir(null) (called on a Context) for a better location that works without permissions and on more Android OS versions.
can't find any examples that are up-to-date
The documentation covers the use of ACTION_SEND.
I am trying to delete a file located at the path
/storage/714D-160A/Xender/image/Screenshot_commando.png
What I've done so far:
try{
String d_path = "/storage/714D-160A/Xender/image/Screenshot_commando.png";
File file = new File(d_path);
file.delete();
}catch(Exception e){
e.printStackTrace();
}
and the file is still at its place(Not deleted :( )
Also I've given permission in Manifest file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.STORAGE" />
public static boolean delete(final Context context, final File file) {
final String where = MediaStore.MediaColumns.DATA + "=?";
final String[] selectionArgs = new String[] {
file.getAbsolutePath()
};
final ContentResolver contentResolver = context.getContentResolver();
final Uri filesUri = MediaStore.Files.getContentUri("external");
contentResolver.delete(filesUri, where, selectionArgs);
if (file.exists()) {
contentResolver.delete(filesUri, where, selectionArgs);
}
return !file.exists();
}
Using ContentResolver to delete media files is wrong and provides many problems for the user.
You can not delete a file on the sd-card simply by deleting its information from the ContentResolver on Android versions greater than Jelly Bean(4.3).
It works only on Android versions prior to KitKat(4.4).
That's why the Android team provided DocumentProvider.
Why contentResolver.delete(...) is wrong?
1. Fills up the sd-card
When you try to delete a media file on the sd-card by the ContentResolver on Android versions greater than 4.3, the actual media file will remain untouched because the contentResolver.delete(...) approach only removes the information (name, date, path ...) of the media and you will end up having unregistered media files on your sd-card which ContentResolver has no idea about their existence anymore and that's why you couldn't see them in your gallery and you think they've been deleted with this approach while they're still there and fill up the sd-card each time the user tries to delete a media file on the sd-card.
2. Media files (Images, videos, gifs ...) will come back to the gallery
There are many apps out there especially gallery and file manager ones that will find these unregistered media files and will add them to the ContentResolver again as of their normal behavior while the user assumes his/her unwanted media files are gone.
Sure no user wants his/her assuming deleted images or videos show up in the middle of a demonstration.
So, what's the correct approach to remove media files on the sd-card?
Well, this has already been answered here with the use of DocumentProvider.
From Android 4.4 onwards, you can't write to SD card files (except in the App directory) using the normal way. You'll have to use the Storage Access Framework using DocumentFile for that.
The following code works for me:
private void deletefile(Uri uri, String filename) {
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, uri);
DocumentFile file = pickedDir.findFile(filename);
if(file.delete())
Log.d("Log ID", "Delete successful");
else
Log.d("Log ID", "Delete unsuccessful");
}
where filename is the name of the file to be deleted and uri is the URI returned by ACTION_OPEN_DOCUMENT_TREE:
private static final int LOCATION_REQUEST = 1;
private void choosePath() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(intent, LOCATION_REQUEST);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == LOCATION_REQUEST && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
if (uri != null) {
/* Got the path uri */
}
}
}
}
Use Environment.getExternalStorageDirectory().getAbsolutePath() instead of hard coding storage path
String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
File f = new File(baseDir + "/714D-160A/Xender/image/Screenshot_commando.png");
boolean d = f.delete();
I'm trying to pass an image that resides in the res/raw directory of my app along with a share intent.
I followed the process described in the FileProvider docs, and here's my code:
AndroidManifest.xml
<application ...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
</application>
res/xml/paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="shared" path="./"/>
</paths>
The code in my activity:
String shareToPackage = ...
File imageFile = new File(context.getFilesDir().getPath() + "/image");
if (!imageFile.exists()) { // image isn't in the files dir, copy from the res/raw
final InputStream inputStream = context.getResources().openRawResource(R.raw.my_image);
final FileOutputStream outputStream = context.openFileOutput("image", Context.MODE_PRIVATE);
byte buf[] = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
imageFile = new File(context.getFilesDir().getPath() + "/image");
}
if (!imageFile.exists()) {
throw new IOException("couldn't find file");
}
final Uri uri = Uri.fromFile(imageFile);
context.grantUriPermission(shareToPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TEXT, "here's the image");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setPackage(shareToPackage);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
The above does not work as the file that I get in the other app isn't accessible:
java.io.FileNotFoundException: FILE_PATH: open failed: EACCES
(Permission denied)
Any idea what I'm doing wrong here?
Thanks.
Get rid of the path attribute in <files-path>, as it is not needed here, since you are serving everything from getFilesDir().
Do not use string concatenation when creating File objects. Replace:
new File(context.getFilesDir().getPath() + "/image.png");
with:
new File(context.getFilesDir().getPath(), "image.png");
Most importantly, do not use Uri.fromFile(). Use FileProvider.getUriForFile(). As it stands, you are going through all this work to set up FileProvider, then you do not use the FileProvider for making the content available to the other app.
Or, get rid of all of this, and use my StreamProvider, which can serve a raw resource directly.
Or, write your own ContentProvider that serves the raw resource directly.
#nitzan-tomer, see https://stackoverflow.com/a/33031091/966789
What Are Runtime Permissions?
With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.
I got this class:
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;
public class MediaScannerWrapper implements
MediaScannerConnection.MediaScannerConnectionClient {
private MediaScannerConnection mConnection;
private String mPath;
private String mMimeType;
// filePath - where to scan;
// mime type of media to scan i.e. "image/jpeg".
// use "*/*" for any media
public MediaScannerWrapper(Context ctx, String filePath, String mime){
mPath = "/sdcard/DCIM/Camera";
mMimeType = "jpg";
mConnection = new MediaScannerConnection(ctx, this);
}
// do the scanning
public void scan() {
mConnection.connect();
}
// start the scan when scanner is ready
public void onMediaScannerConnected() {
mConnection.scanFile(mPath, mMimeType);
Log.w("MediaScannerWrapper", "media file scanned: " + mPath);
}
public void onScanCompleted(String path, Uri uri) {
// when scan is completes, update media file tags
}
}
How to use it in the other class?
I don't know how to properly use classes, I tried but nothing is working.
I do something wrong, but I don't know what, can someone help me with this.
The Story
Before Android 4.4, we could just send a broadcast to trigger the media scanner on any particular file, or folder or even on the root of the storage. But from 4.4 KitKat, this have been fixed by the Android Developers.
Why do I say fixed? The reason is simple. Sending a broadcast using MEDIA_MOUNTED on the root directory is very expensive. Running the Media Scanner is an expensive operation and the situation gets even worse when the user has got a lot of files in the storage and deep folder structures.
Before Android 4.4
Keep it straight and simple. If you are targeting your app before Android 4.4. But keep in mind not to use it on the root directory unless absolutely necessary.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
From Android 4.4
There are two ways for you.
i) The first one is very similar to the previous example, but may not work efficiently and is not recommended too.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
ii) Now, let us move on to the most recommended and efficient solution to this problem.
Add the file paths of the files which have been updated, like this, in a String type ArrayList
ArrayList<String> toBeScanned = new ArrayList<String>();
toBeScanned.add(item.getFilePath());
Now you need to run scanFile() static method of the MediaScannerConnection class and pass the String array containing the list of all the files which have been updated and needs to be media scanned.
You can also put a listener to respond when the scanning has been finished for individual files.
String[] toBeScannedStr = new String[toBeScanned.size()];
toBeScannedStr = toBeScanned.toArray(toBeScannedStr);
MediaScannerConnection.scanFile(getActivity(), toBeScannedStr, null, new OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
System.out.println("SCAN COMPLETED: " + path);
}
});
Hey I found out how to do it with a very simple code.
Just call this line of code:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
This should trigger mediascanner.
In Android, there is a content database which is used by the media scanner to keep track of all the media content present on the device.
When Android boots up, the mediascanner service is launched and runs through the entire external storage to find if there is any new media content if it finds one then,
It adds an entry of that media content into the content database
Each entry in the content database contains metadata of the media content like Name, date, file size, type of file, etc..
So when you make a modification to a media content, you will need to update the content database also.
If the content database is not update then other applications also will not be able to access that particular media content.
Running the media scanner just updates the content database
Instead of running the media scanner, you can update the content database yourself and it should resolve the problem.
Here is an explanation on how to insert, delete, update using the content resolver. (Search for the section "Inserting, Updating, and Deleting Data")
Edit:
There is a sample code in this answer. Check for the answer by Janusz.
File file = new File(absolutePath);
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
sendBroadcast(intent);
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);
}
Reference: http://developer.android.com/training/camera/photobasics.html#TaskGallery
The Add the Photo to a Gallery Section
As #Aritra Roy's answer, i decide to make an experiment about this issue.
What i got here are:
Intent.ACTION_MEDIA_MOUNTED and Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
can accept individual file path, so sendBroadcast(new
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(filePath)));
or sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse(filePath))); will be valid.
If you use individual file path with Intent.ACTION_MEDIA_MOUNTED on Kitkat or above, your application will still crash
If you use Intent.ACTION_MEDIA_SCANNER_SCAN_FILE or MediaScannerConnection on device lower than Kitkat, your application will not force close, but the method will just simply not working as you want.
From that experiment, i think the best method to handle is
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
MediaScannerConnection.scanFile(context, new String[]{imagePath}, null, new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
//something that you want to do
}
});
} else {
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + imagePath)));
}
Let me know if i missed something