I'm operating on the MediaStore directly and add/remove files from there whenever my app add/removes files. I want fast and instant MediaStore updates and the support of batch operations, that's why I do that. Here's an example of an add operation:
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
File f = new File(path);
ContentValues values = new ContentValues(7);
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
values.put(MediaStore.Images.Media.DATE_MODIFIED, dateModified / 1000L);
values.put(MediaStore.Images.Media.MIME_TYPE, ExtensionUtil.getMimeType(fileName));
values.put(MediaStore.Images.Media.ORIENTATION, rotation);
values.put(MediaStore.Images.Media.DATA, filePath);
if (latitude != null || longitude != null)
{
values.put(MediaStore.Images.Media.LATITUDE, latitude);
values.put(MediaStore.Images.Media.LONGITUDE, longitude);
}
ContentProviderOperation operation = ContentProviderOperation.newInsert(uri)
.withValues(values)
.build()
Here's an delete operation:
String columnData = MediaStore.Images.Media.DATA;
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentProviderOperation operation = ContentProviderOperation.newDelete(uri)
.withSelection(columnData + "=?", new String[]{path})
.build();
With this sort of operations I'm able to handle all cases and execute them in batches as I desire.
Problem
Hide a folder. If I hide a folder, I create a .nomedia file in it, afterwards I want to remove the entries of all medias in it from the MediaStore, BUT KEEP the files on the storage of course. Any ideas how I could create operations that do not delete the file as well?
I don't want to use the MediaScanner, I'm optimising speed and I need the operations so that I can call a lot of them in a batch if possible...
Related
Hi I am new to android development and have been trying to accomplish the above said functionality.
I am testing app on Android 9, API 28. I am able to save captured image to folder but not been able to display it in gallery (Like WhatsApp).
I have tried:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
OutputStream os;
String[] split = imagePathNew.split("\\.");
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, split[0] + ".jpg");
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + "Test");
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
os = (OutputStream) resolver.openOutputStream(Objects.requireNonNull(imageUri)); // imageLocalUri is the uri of captured image in folder
Bitmap bitmap = MediaStore.Images.Media.getBitmap(resolver, imageLocalUri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
Objects.requireNonNull(os);
} else {
Intent updateInGallery = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
updateInGallery.setData(imageLocalUri); // imageLocalUri is the uri of captured image in folder
context.sendBroadcast(updateInGallery);
}
Can someone please help me with what am I doing wrong here?
Nothing is wrong with the posted code.
In the addition use following class
https://developer.android.com/reference/android/media/MediaScannerConnection#scanFile(java.lang.String,%20java.lang.String)
From docs
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.
I'm trying to insert audio file to shared storage in Android. I'm getting error on api 29(emulator).
Error :
java.lang.IllegalArgumentException: Primary directory (invalid) not allowed for content://media/external_primary/audio/media; allowed directories are [Alarms, Music, Notifications, Podcasts, Ringtones]
My Code is:
...
Uri collection = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
? MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
: MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
values = new ContentValues();
values.put(MediaStore.Audio.Media.DISPLAY_NAME, targetFileName);
values.put(MediaStore.Audio.Media.RELATIVE_PATH, targetFileDirPath);
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mpeg");
values.put(MediaStore.Audio.Media.IS_PENDING, 1);
resolver = getContentResolver();
uri = resolver.insert(collection, values); // error throws from here
outputStream = uri != null ? resolver.openOutputStream(uri) : null;
...
What is the cause of this error and how can I solve this problem?
Apparently, MediaStore.Audio.Media.getContentUri() does not return a directly-usable Uri, at least on Android 10+. It points to an abstract location for "audio", but you cannot write content directly to that Uri. Instead, you need to use RELATIVE_PATH to specify one of the supported collections (Alarms, Music, Notifications, Podcasts, Ringtones), and then any path that you want inside of there.
Note, though, that RELATIVE_PATH itself is new to Android 10. For Android 9 and older devices, I recommend just writing to the filesystem directly.
{ Confirm usage of #CommnWare
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + FILE_DIR);
values.put(MediaStore.MediaColumns.IS_PENDING, 1);
values.put(MediaStore.MediaColumns.DISPLAY_NAME, FILE_NAME);
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");enter code here
ContentResolver resolver = _context.getContentResolver();
resolver.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), values);
}
While this code successfully creates an image that is also present in the phone's gallery, the extension is '.jpg' instead of '.gif'.
File gifFile; // gif file stored in Context.getFilesDir()
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "Image" + System.currentTimeMillis());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/gif");
// Create a new gif image using MediaStore
final Uri gifContentUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
// Open a writable stream pointing to the new file created by MediaStore
OutputStream outputStream = context.getContentResolver().openOutputStream(gifContentUri, "w");
// Copy the original file from the app private data folder to the file created by MediaStore
IOUtils.copyFile(new FileInputStream(gifFile), outputStream);
Output file is created inside Pictures folder by MediaStore. If I manually change the output file's extension to gif, the gif animation is playing inside Android gallery.
I feel I'm missing a small detail for this to work
Removed the DISPLAY_NAME line.
Add contentValues.put(MediaStore.MediaColumns.DATA, "/storage/emulated/0/Pictures/Image." + System.currentTimeMillis() + ".gif");
It goes to a subdir of the Pictures directory if the subdir exists contentValues.put(MediaStore.MediaColumns.DATA, "/storage/emulated/0/Pictures/Mine/Image." + System.currentTimeMillis() + ".gif");.
For Android Q the DATA column is useless.
String displayName = "Image." + System.currentTimeMillis() + ".gif";
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
will do it there.
I'm trying to set the android default ringtone or notification tone via content provider from my assets folder.
Surprisingly, it works like this, but is it a legitimate way?
Uri audiouri = Uri.parse("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, audiouri );
Unfortunately, the sound name isn't shown in Android settings.
Strangely the sound name is actually shown when I go to 'Other sounds'
I also tried this:
Uri audiouri = Uri.parse("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.TITLE, soundname);
Uri ringtoneuri = a.getContentResolver().insert(audiouri, contentValues);
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, ringtoneuri);
resulting in a null sound (no sound is set)
third option I tried is:
Uri audiouri = MediaStore.Audio.Media.getContentUriForPath("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DATA, "content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
contentValues.put(MediaStore.MediaColumns.TITLE, soundname);
Uri ringtoneuri = a.getContentResolver().insert(audiouri, contentValues);
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, ringtoneuri);
Now the sound name is shown correctly, but no sound is actually played.
I get error on logcat:
java.io.FileNotFoundException: Can't access /content:/com.mydomain.myapp/test.mp3
So it seems it's taking the value from MediaColumns.DATA which does not support Content provider paths but only real paths. Right?
Final question: How to set tone AND name in android settings? Preferably without copying the file to external storage.
So, unfortunately I did not find out how to set asset as ringtone directly,
but this is a nice workaround:
When copying asset to internal app storage or cache dir (no permissions needed for that!) I was able to set the ringtone without WRITE_EXTERNAL_STORAGE permisson.
static void settone(int type, Sound sound, Activity a)
{
lastsound = sound; //global remember sound and type (alarm/ringtone/notification)
lasttype = type; // if we have to get permissions first, then call this from onActivityResult
if (canwritesystem(a))
{
RingtoneManager.setActualDefaultRingtoneUri(a, type, getringtoneuri(sound, a));
Toast.makeText(a, a.getString(R.string.settonesuccess), Toast.LENGTH_LONG).show();
}
else a.startActivityForResult(new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).setData(Uri.parse("package:" + a.getPackageName())),CONTEXT_SET_TONE);
}
static Uri getringtoneuri(Sound sound, Activity a)
{
File tonefile = new File(sound.getpath); // path could be like: /Android/data/com.company.yourapp
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DATA, tonefile.getAbsolutePath());
contentValues.put(MediaStore.MediaColumns.TITLE, sound.getDisplayName());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
contentValues.put(MediaStore.MediaColumns.SIZE, tonefile.length());
contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, true);
contentValues.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
contentValues.put(MediaStore.Audio.Media.IS_ALARM, true);
contentValues.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri generalaudiouri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
a.getContentResolver().delete(generalaudiouri, MediaStore.MediaColumns.DATA + "='" + tonefile.getAbsolutePath() + "'", null);
return a.getContentResolver().insert(generalaudiouri, contentValues);
}
I have a code inside some function of my activity:
ContentValues cv = new ContentValues();
cv.put(MediaStore.Images.Media.TITLE, "1354213408296.jpg");
ContentResolver contentResolver = getContentResolver();
Uri imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv);
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
cameraIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(cameraIntent, 712984419/*Some request code*/);
It crashes with:
java.lang.IllegalStateException: Unable to create new file:
/mnt/sdcard/DCIM/Camera/1354213408296.jpg at
android.os.Parcel.readException(Parcel.java:1335) at
android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:182) at
android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:136) at
android.content.ContentProviderProxy.insert(ContentProviderNative.java:415) at
android.content.ContentResolver.insert(ContentResolver.java:730)
crashes on:
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv);
The "1354213408296.jpg" is just System.currentTimeInMillis() + ".jpg", so it is always unique
The android.permission.WRITE_EXTERNAL_STORAGE is provided in manifest
Here is some phone's environment specifications (I am using ACRA to get it):
getDataDirectory=/data
getDownloadCacheDirectory=/cache
getExternalStorageAndroidDataDir=/mnt/sdcard/Android/data
getExternalStorageDirectory=/mnt/sdcard
getExternalStorageState=removed
getRootDirectory=/system
getSecureDataDirectory=/data
getSystemSecureDirectory=/data/system
is4GConfig=true
is8GConfig=false
isEncryptedFilesystemEnabled=false
isExternalStorageEmulated=false
isExternalStorageRemovable=true
What can I do to prevent this crashes?
I'm not sure what you're trying to do. All you seem to be doing is trying to create a new row in MediaStore.Images.Media, with only a TITLE column. Putting in a title without the data to go with it doesn't make much sense.
This seems to be just another exception you will get when no sdcard is present (I was able to reproduce it only on very weird emulators, but who knows?). Cases of missing sdcard should be handled for sure. My current solution is as follows:
public static Uri getImageFileUri(Context context) throws IOException{
String fullFileName = generateImageFileName(imageName); // a method i have defined
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, fullFileName);
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return Uri.fromFile(new File(fullFileName));
}
}
This is the method I use to generate the Uri I will start the camera intent with. Afterwards I use it exactly like you do. The thing is that the value I return in the no sdcard case will not work properly, but on the other hand Android devices do not allow taking pictures if no sdcard is present. Using this solution you will succeed in taking picture if there is a sdcard and will launch the native camera that will show message "Insert sdcard in order to take picture" in the other cases.