I want my app to set a ringtone. The user has selected one from the existing library entries before using the system picker and gets this extra back: RingtoneManager.EXTRA_RINGTONE_PICKED_URI
In this example I have picked "Andromeda" from the default ringtones and get this path: content://media/internal/audio/media/103
When I try to set it at a given time I run this code:
Uri ur = Uri.parse(ringtoneFile.getAbsolutePath()); RingtoneManager.setActualDefaultRingtoneUri(context, ringtoneType, uri);
I have also tried this version:
Uri ur = Uri.parse(ringtoneFile.getAbsolutePath()); android.provider.Settings.System.putString(context.getContentResolver(), android.provider.Settings.System.RINGTONE, uri.toString());
Neither works. The system's sound settings will look like this:
Only 103 is shown, not "Andromeda" as I would expect. When I have the emulator called it just makes a ding sound, so it probably can't play the desired file and uses some fallback one.
There are plenty of examples here where people pick a custom file from the filesystem and add that to the library anew using "ContentValues". But I do not want to add anything myself, I just want to set one from the default ringtones.
As an alternative I have tried to code as well. It does add an additional entry to the library. Unfortunately old ones are not deleted, but pile up. Also I get the same ding sound when calling the emulator, not the one I selected.
private boolean applyRingTone(File ringtoneFile, int ringtoneType, Context context)
{
Miscellaneous.logEvent("i", "Profile", "Request to set ringtone to " + ringtoneFile.getAbsolutePath(), 3);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, ringtoneFile.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, context.getResources().getString(R.string.app_name) + " ringtone");
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/*");
values.put(MediaStore.MediaColumns.SIZE, ringtoneFile.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, ringtoneType == RingtoneManager.TYPE_RINGTONE);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, ringtoneType == RingtoneManager.TYPE_NOTIFICATION);
values.put(MediaStore.Audio.Media.IS_ALARM, false);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri ur = MediaStore.Audio.Media.getContentUriForPath(ringtoneFile.getAbsolutePath());
context.getContentResolver().delete(ur, MediaStore.MediaColumns.DATA + "=\"" + ringtoneFile.getAbsolutePath() + "\"", null);
Uri uri = context.getContentResolver().insert(ur, values);
try
{
RingtoneManager.setActualDefaultRingtoneUri(context, ringtoneType, uri);
Miscellaneous.logEvent("i", "Profile", "Ringtone set to: " + uri.toString(), 1);
return true;
}
catch (Throwable t)
{
String message = "Error setting ringtone: " + Log.getStackTraceString(t);
Miscellaneous.logEvent("e", "Profile", message, 1);
}
return false;
}
I haven't quite solved my problem, yet, but at least spotted the culprit.
The way to set the file as ringtone does work. However the path of the source file is incorrect. That is pretty weird because it is determined by using a filepicker:
Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT);
fileIntent.setType("audio/*");
startActivityForResult(Intent.createChooser(fileIntent, "Select a ringtone"), intentCodeRingtonePickerCallsFile);
This returns as path:
content://com.android.externalstorage.documents/document/primary%3AMusic%2Fmusicfile.mp3
After converting it to a File object it'll become
/document/primary%3AMusic%2F04.%20Elevator%20Girl.mp3
When just hard-coding the path into the sourcecode (in a format a regular Linux user would assume it needs to have) it is actually simply:
/sdcard/Music/musicfile.mp3
And that works flawlessly. I'll have to figure out how to correctly determine the path, but the method to set a file as ringtone is functional.
UPDATE: I have an answer to my path problem: Get Real Path For Uri Android
Related
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);
}
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 am using Jaudiotagger 2.2.5 for an android music tagging app. I am able to change metadata like album name, artist name, genre etc. But no matter what I try I can't get the album art part working. I have exhausted all suggestions that I could find online, but nothing seems to work. Jaudiotagger itself lacks documentation nor is the developer very helpful answering such issues.
for(Song s : songlist){ //for each song in the album
File file = new File(artUri);
if(file.exists()) {
Artwork cover = ArtworkFactory.createArtworkFromFile(file);
tag.deleteArtworkField();
tag.createField(cover);
tag.setField(cover);
af.commit();
}
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(f));
sendBroadcast(intent);
}
Additionally I am using another method to update the mediastore:
public void updateAlbumArtMediaStore(Context context, final long id, String art){
Uri uri = ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), id);
context.getContentResolver().delete(uri,null, null);
ContentValues values = new ContentValues();
values.put("album_id", id);
values.put("_data", art);
Uri newuri = context.getContentResolver()
.insert(Uri.parse("content://media/external/audio/albumart"),
values);
if(newuri!=null){
Toast.makeText(AlbumTagEditorActivity.this, "UPDATED", Toast.LENGTH_LONG).show();
context.getContentResolver().notifyChange(uri, null);
}else{
Toast.makeText(AlbumTagEditorActivity.this, "FAILED", Toast.LENGTH_LONG).show();
}
}
But when I do this it only deletes the cover art.
Its been months since I am trying to get this right. Nothing I have tried ever worked.
I am using the DownloadManager to download an image to the system's gallery and then in the Broadcast receiver (once the download succeeds) using an Intent to set the image as the wallpaper.
Everything was working fine but then recently on 4.4 I started to get an exception in the Photos/Google+ app because it is expecting a content URI and not a file URI.
So my question is if anyone knows how to convert a full file path/URI (file://) into a content style URI (content://)?
Sorry for the lack of source code, I am away from the computer that has the source, but I hope the question makes sense without it, get a content style uri from a full path.
EDIT:
The image is copied into the system's gallery or media gallery, not saved within my apps internal storeage.
Here is an example of what I want to convert:
file:///storage/emulated/0/Pictures/Rockstar/image.jpg
to
content://media/internal/images/media/445
EDIT 2:
Here is the error that I get from the Google+ app:
04-21 10:50:35.090: E/AndroidRuntime(7220): FATAL EXCEPTION: main
04-21 10:50:35.090: E/AndroidRuntime(7220): Process: com.google.android.apps.plus, PID: 7220
04-21 10:50:35.090: E/AndroidRuntime(7220): java.lang.RuntimeException: Unable to resume activity
{com.google.android.apps.plus/com.google.android.apps.photos.phone.SetWallpaperActivity}:
java.lang.IllegalArgumentException: Image URI must be of the content scheme type
Here is the code that I use to let the user set the wallpaper:
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Uri u = Uri.parse(uriString);
Intent wall_intent = new Intent(Intent.ACTION_ATTACH_DATA);
wall_intent.setDataAndType(u, "image/*");
wall_intent.putExtra("mimeType", "image/*");
Intent chooserIntent = Intent.createChooser(wall_intent,
"Set As");
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(chooserIntent);
}
Where uriString is:
file:///storage/emulated/0/Pictures/Rockstar/image.jpg
I was able to figure it out. It was a combination of the code found here: Converting android image URI and scanning the media file after downloading.
So after the file finished downloading I get the path and do the following:
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
//Update the System
Uri u = Uri.parse(uriString);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, u));
//Get the abs path using a file, this is important
File wallpaper_file = new File(u.getPath());
Uri contentURI = getImageContentUri(context, wallpaper_file.getAbsolutePath());
For some reason starting the media scanner, newing the file, and getting the absolute path are important, I'm not exactly sure why but I can't spend any more time on this!
The way to convert from a file URI to a content URI is as follows (taken from the linked StackOver flow post:
public static Uri getImageContentUri(Context context, String absPath) {
Log.v(TAG, "getImageContentUri: " + absPath);
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
, new String[] { MediaStore.Images.Media._ID }
, MediaStore.Images.Media.DATA + "=? "
, new String[] { absPath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI , Integer.toString(id));
} else if (!absPath.isEmpty()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, absPath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
Maybe this will help someone in the future.
So my question is if anyone knows how to convert a full file path/URI (file://) into a content style URI (content://)?
Implement a ContentProvider. FileProvider offers an out-of-the-box solution for serving up local files.
I'm not sure about the technique you are using to set the wallpaper but the easiest way is probably to use WallpaperManager.setStream() which doesn't require any URI.
Also note that a file URI only works between apps if the file is publicly accessible so a content URI is a more general solution.
Using a content URI implies that a ContentProvider will serve the file. Which one depends on where your file is located.
If your app has a direct read access to the file, you can implement a content provider in your app by using for example the FileProvider class of the support library, but this should really only be used if the file is located in the private data storage of your app.
If the image is added to the system media gallery, you should probably use the URI provided by the MediaStore.
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.