Why an Android may delete data saved under Media.EXTERNAL_CONTENT_URI after unmount/mount SD card action? How to avoid this?
I save ringtones using this URI and the default content resolver from context.
The code is similar to that:
ContentValues values = new ContentValues();
values.put(Media.DATA, AudikoFileStorageAccessor.getInstance().getAbsolutePathForRingtone(ringtone.getId()));
values.put(Media.TITLE, ringtone.mSong);
values.put(Media.DISPLAY_NAME, ringtone.mSong);
values.put(Media.ARTIST, ringtone.mArtist);
values.put(Media.MIME_TYPE, "audio/mpeg");
values.put(Media.SIZE, ringtone.mSize * 1024);
values.put(Media.IS_RINGTONE, (RingtoneManager.TYPE_RINGTONE == type || type == 0));
values.put(Media.IS_NOTIFICATION, (RingtoneManager.TYPE_NOTIFICATION == type));
values.put(Media.IS_ALARM, (RingtoneManager.TYPE_ALARM == type));
values.put(Media.IS_MUSIC, false);
Uri newUri = mContext.getContentResolver().insert(
Media.getContentUriForPath(AudikoFileStorageAccessor.getInstance().getAbsolutePathForRingtone(
ringtone.getId())), values);
RingtoneManager.setActualDefaultRingtoneUri(mContext, type, newUri);
Everything works fine, but after I unmount SD card and then mount it again this ringtone doesn't exist anymore and couldn't be found within this table.
Should I handle UNMOUNT event and backup somehow my saved data and restore it as soon as SD card is available again?
I've found the mistake. Hope someone would find this useful. If you want to keep your media after SD card was unmounted/mounted you need to notify about new media like that just after you save it:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
If you do that, after you mount SD card back again your media will be still available.
Related
I'm attempting to send an image to Hangouts from within an app I'm building.
I'm working in Xamarin for VS 2015 to do this so the code below is c# but it's not much different from the equivalent Java code so I think it's easy to follow.
What I've done is set up a button on my app which has code setting up an Intent to share an image to Hangouts. I've set the image up already in the Downloads folder on the device and hardcoded the name into the code.
Intent hangoutsShareIntent = new Intent(Intent.ActionSend);
hangoutsShareIntent.SetType("image/jpeg");
hangoutsShareIntent.SetPackage("com.google.android.talk");
string downloadsPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
string filePath = Path.Combine(downloadsPath, "shared.jpg");
hangoutsShareIntent.PutExtra(Intent.ExtraStream, filePath);
StartActivity(Intent.CreateChooser(hangoutsShareIntent, "Share with"));
When I run this, I get the option to select a chat in Hangouts that I want to send the content to. Upon selecting the chat, I get a blank message box and no image.
I've swapped the above code over to use text/plain and pass the filePath variable to the message. When I copy the file path into Chrome to check it, the image loads so I have to figure that the image is where I've said it is... right?
I get no errors (probably because the issue is in Hangouts rather than my app so I have nothing to debug there). Logcat shows nothing except an error I can't find much about on Google: ExternalAccountType﹕ Unsupported attribute readOnly
The only information I could find on that error implied some issue with permissions but I've made sure my app has runtime permissions checked for Read/Write using this code (which wraps the above):
if ((CheckSelfPermission(Permission.ReadExternalStorage) == (int)Permission.Granted) &&
(CheckSelfPermission(Permission.WriteExternalStorage) == (int)Permission.Granted))
NOTE: I'm running this on a HTC One M8 - no SD card but does have external storage on device. I've also added the above permissions to the manifest for earlier Android versions.
The documentation for this (here) isn't overly helpful either so any advice AT ALL here is welcome :)
Thanks!
If you use the file provider instead of sending just the URI on its own. This should get around the permission issues you are seeing.
There is a guide available here which might be useful.
Intent shareIntent = new Intent(Intent.ActionSend);
shareIntent.SetType("image/gif");
Java.IO.File file = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory + "/myimage.gif");
Android.Net.Uri fileUri = Android.Support.V4.Content.FileProvider.GetUriForFile(this, "com.myfileprovider", file);
shareIntent.SetPackage("com.google.android.talk");
shareIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
shareIntent.PutExtra(Intent.ExtraStream, fileUri);
StartActivity(Intent.CreateChooser(shareIntent, "Share with"));
So guys, this is a big one
I'm currently working on an App which involves a big database creation.
Basicalls what it does is scanning the entire device for any kind of media file and adds it to the database (filtered by certain file extensions).
The way it currently does that is scanning through the device's folders with a recursive function, and every time it finds a file that is actually a file and not a directory AND matches the file extensions I picked it adds the file's path to a previously created ArrayList containing only strings.
This is implemented in an AsyncTask so it's done in the background.
As soon as that task finishes it passes the ArrayList on to another AsyncTask that is responsible for adding those files to the database. Out of simplicity resons I only took the file's path, its name and the title that is stored in its tags for every entry (simple sqlite stuff) and displayed the titles in a ListView in another activity over cursor adapters and so on (which isnt really an issue in my case).
Right now it workes pretty well, doesn't crash or return anything bad (I'm testing on a OnePlus One right now).
BUT I have a really big music library on my phone (around 5400 songs) which is nearly filling my entire storage. And out of some unknown reason to me, the scanning task i mentioned (with the ArrayList) stops at around 3000 files and passes the ArrayList on to the second task, which is obviously not what I'm aming at to have.
My guess is that there might be a big performance issue and using an ArrayList is not the best way to approach this. It might work with a number of files that is under 3000 but higher than that is also necessary...
So what would you guys suggest? Does my approach lack something or do I have to try something completely different?
You don't need scan your folders (and, probably, creating database of media files too), you can just use MediaStore class
The Media provider contains meta data for all available media on both internal and external storage devices.
For example, this piece of code will return all available audio playlists
private List<Playlist> getPlayListsList(){
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
String[] columns = { _ID, NAME };
Cursor playLists = resolver.query(uri, columns, null, null, null);
List<Playlist> result = new ArrayList<>();
if (playLists == null) {
return result;
}
Cursor cursor;
for (boolean hasItem = playLists.moveToFirst(); hasItem; hasItem = playLists.moveToNext()) {
long id = playLists.getLong(playLists.getColumnIndex(_ID));
String name = playLists.getString(playLists.getColumnIndex(NAME));
cursor = resolver.query(MediaStore.Audio.Playlists.Members.getContentUri("external", id),
new String[]{MediaStore.Audio.Playlists.Members.TITLE, MediaStore.Audio.Playlists.Members.ARTIST,
MediaStore.Audio.Media.DATA, MediaStore.Audio.Playlists.Members.PLAY_ORDER}, null, null, null
);
int songsCount = cursor.getCount();
result.add( new Playlist(
id,
name,
songsCount));
cursor.close();
}
playLists.close();
return result;
}
I have an issue refreshing the gallery on the android device.
Android device being used to debug with is a Galaxy Note 3 with NO sd card.
After deleting a file, a refresh has to be done on the entire gallery otherwise the image is still visible in the gallery of the phone even though it has been deleted (because there isn't a better way to do this for the deletion of a file to my knowledge)
But when I do the refresh the app force closes. I would guess that it has something to do with the phone not having a SD card to mount/unmount.
Is there a way to refresh the gallery besides this piece of code? Or do you know why the application would force close after the delete?
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
I've also tried playing with that code as to change it to something like this, but still the error persists.
String selectedFile = getRealPathFromURI(selectedURI);
me.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse(selectedFile)));
private String getRealPathFromURI(Uri contentURI) {
Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) { // Source is Dropbox or other similar local file path
return contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
return cursor.getString(idx);
}
}
Also just for a last resort I tried this. The app didn't crash, but didn't scan the file / or fix the problem either.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
EDIT: I have added the permissions in the Manifest only for
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Found how to fix this.
You have to execute this on the main thread. There might be a way around this and if some one would add to this then it would be great.
So Fix : This has to be executed in your main activity for some reason.
My development phone is a Nexus 5, running Android 4.4.2.
In my application, I am attempting to create a folder on external storage that will store debug information for my application. Basically it will contain all the commands executed by the application, so that when a user encounters a problem, I have the option of having them send me the information from the debug folder to analyse.
I started off by trying to write a file to the folder, but found there was an error creating the folder. At first I was using mkdir(), then I moved onto mkdirs() which also didn't work.
I have <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> in my manifest.
Here is the code for creating the folder:
File folder = new File(Environment.getExternalStorageDirectory() + "/DebugData");
String path = folder.getPath();
if(!folder.mkdirs() || !folder.exists()){
Log.e(LOG_TAG, path + " failed");
} else {
Log.d(LOG_TAG, path + " succeeded");
}
Here is what I have also tried:
//Check SD card state
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) || !Environment.MEDIA_MOUNTED.equals(state)) {
Log.e(LOG_TAG, "Error: external storage is read only or unavailable");
} else {
Log.d(LOG_TAG, "External storage is not read only or unavailable");
}
This returns that the external storage is not read only or unavailable.
I have also tried different paths, such as File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Folder1");
This is where it became really confusing.
I tried development on different phones. Firstly, I grabbed a Galaxy S4 GT-i9505 running Android 4.2.2 and it worked. I was able to create the folders and write to them. This showed me that the code was working. Also the path returned by running the code on the S4 and Nexus 5 was the same.
Then I thought it may be android version specific. So I grabbed a Nexus 4 with Android 4.4.2 and the code worked on it as well. Created the folders and allowed me to write to them.
None of the phones are rooted and are all stock standard. There's no special applications or anything I can think of settings wise on the Nexus 5 that would cause permissions problems. The connection is set to Media Device (MTP).
EDIT:
I should add that I have tried the follow which also did not work:
Writing a file to the root directory of the external storage
Creating the file in the external storage root directory and writing to it
Creating a folder in a path outlined and writing a file to it
Creating the file in the path outlined and writing to it
I am confused as to what is causing this, is there anything else I can test or change to fix the issue?
EDIT 2:
Turns out the issue was due to, I think, indexing.
Basically all of the other devices I tested on, allowed me to reconnect the USB connection and view the created files and folders.
For some reason my Nexus 5 doesn't index the folders/files, even though they exist.
I downloaded a different 3rd party file explorer application and noticed all the folders and files were there.
So to view these folders and files via USB debugging, I have to restart the phone in order to re-index them, which seems quite annoying but it is better than it not working at all.
Thanks.
In terms of this being an indexing issue with the Nexus, this worked for me:
MediaScannerConnection.scanFile(this, new String[] { file.toString() }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
You should call it straight after creating and saving the file. By using the scanner, I was able to see newly created files and directories simply by replugging the device in.
According to the docs:
MediaScannerConnection provides a way for applications to pass a newly
created or downloaded media file to the media scanner service. The
media scanner service will read metadata from the file and add the
file to the media content provider.
Hope this helps someone else.
Turns out the issue was due to, I think, indexing.
Basically all of the other devices I tested on, allowed me to reconnect the USB connection and view the created files and folders.
For some reason my Nexus 5 doesn't index the folders/files, even though they exist.
I downloaded a different 3rd party file explorer application and noticed all the folders and files were there.
So to view these folders and files via USB debugging, I have to restart the phone in order to re-index them, which seems quite annoying but it is better than it not working at all.
For android sdk version 23 and above you should check if the user has granted permission of external storage.
private void createFolder() {
if (isStoragePermissionGranted()) {
File folder = new File(Environment.getExternalStorageDirectory()+ File.separator + "DebugData");
if(!folder.exists()){
folder.mkdir();
}
}
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
return true;
}
}
The code above worked for me and I hope it will work for you.
Had the same problem. Turned out I was testing on Android 6 and did not check for runtime WRITE_EXTERNAL_STORAGE permission.
First, new File() is just create object for file connect.
you want use this file, file is exists or createNewFile().
If you want use this file to create directory, check file is exists, isDirectory() after then mkDir().
Second, check Environment.getExternalStorageDirectory is variable path.
You can use DDMS or ADB for work.
Additionaly, I think you add permission for read external storage for something error.
If you are running an Android device with api >= 23, you need to request permission from user before you call mkdir() function.
My previous answer with example code can be found
https://stackoverflow.com/a/38694026/5089713
For Android 10+ add following into manifest:
<application
android:requestLegacyExternalStorage="true"
...
as asking for Manifest.permission.WRITE_EXTERNAL_STORAGE is no more enough.
Typically, I do this:
/**
* Return a dedicated directory named "MyApp" at the top of external storage
*/
public static file getDataDir() {
File sdcard = Environment.getExternalStorageDirectory();
if( sdcard == null || !sdcard.isDirectory() ) {
// TODO: warning popup
Log.w(TAG, "Storage card not found " + sdcard);
return null;
}
File dataDir = new File(sdcard, "MyApp");
if( !confirmDir(dataDir) ) {
// TODO: warning popup
Log.e(TAG, "Unable to create " + dataDir);
return null;
}
return dataDir;
}
private static boolean confirmDir(File dir) {
if (dir.isDirectory()) return true; // already exists
if (dir.exists()) return false; // already exists, but is not a directory
return dir.mkdirs(); // create it
}
Also, add this permission to your manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
If you want private storage (typically under /data/) for your app (not on sdcard), then look into getDir(), getFilesDir(), fileList(), openFileInput(), openFileOutput(), etc.
There are also helper functions to get private directories within the sdcard for API 8 and above: getExternalFilesDir(), getExternalCacheDir(), etc.
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) {
...
}