Alternative way to load ringtone title in android - android

I am developing an application where i need all the ringtone title and uri. I just need the title but not the Ringtone object. A well known way to get ringtone uri and title is following
rm = new RingtoneManager(getActivity());
rm.setType(RingtoneManager.TYPE_RINGTONE);
Cursor cursor = rm.getCursor();
if(cursor.moveToFirst()){
do{
try{
Uri uri= rm.getRingtoneUri(cursor.getPosition());
Ringtone rtone = rm.getRingtone(cursor.getPosition());
System.out.printf("URI: " + uri.toString());
System.out.printf("Title: " + rtone.getTitle(this));
}
catch(Exception ee){}
}while(cursor.moveToNext());
}
My problem is Ringtone object. It's costly to create Ringtone object each time. Ringtone object also create media player. So when i load all the list it's takes little bit time. I have also tested the code without creating the Ringtone object and it's fair enough.

Related

How to update metadata of audio file in Android Q media store?

Updating metadata of audio file in media store is not working in Android Q OS, it works in all other OS.
I am using content provider with uri specified as MediaStore.Audio.Media.EXTERNAL_CONTENT_URI. It is working fine in all below Android Q device. Below is the code that I am using to update track metadata.
ContentValues cv = new ContentValues();
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
cv.put(MediaStore.Audio.Media.TITLE, newTitle);
cv.put(MediaStore.Audio.Media.ALBUM, newAlbumName);
cv.put(MediaStore.Audio.Media.ARTIST, newArtistName);
int rowsUpdated = resolver.update(uri, cv,
MediaStore.Audio.Media._ID + " = ? ", new String[]{audioId});
For Android Q device, rowsUpdated is always 0 with no exception.
How are other music player updating tracks metadata in Android Q ?
Finally, it took some time but I figured that out.
First, you need to obtain access to file. Here you can read about that
Next, I found out that to update title or artist fields (maybe others to, I didn't test them) you need to set column MediaStore.Audio.Media.IS_PENDING value to 1. Like that:
val id = //Your audio file id
val values = ContentValues()
values.put(MediaStore.Audio.Media.IS_PENDING, 1)
val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
contentResolver.update(uri, values, null, null)
And then you can edit fields that you need. Also to end the update process set MediaStore.Audio.Media.IS_PENDING to 0 again:
val id = //Your audio file id
val title = //New title
val artist = //New artist
val values = ContentValues()
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
values.put(MediaStore.Audio.Media.TITLE, title)
values.put(MediaStore.Audio.Media.ARTIST, artist)
val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
contentResolver.update(uri, values, null, null)
So in one function, it would look like this:
#RequiresApi(value = android.os.Build.VERSION_CODES.Q)
fun updateMetadata(contentResolver: ContentResolver, id: Long, title: String, artist: String) {
val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
val values = ContentValues()
values.put(MediaStore.Audio.Media.IS_PENDING, 1)
contentResolver.update(uri, values, null, null)
values.clear()
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
values.put(MediaStore.Audio.Media.TITLE, title)
values.put(MediaStore.Audio.Media.ARTIST, artist)
contentResolver.update(uri, values, null, null)
}
It's written in Kotlin but I think you will figure out how to do that in java.
UPDATE
By updating MediaStore you don't updating real file at any android version. That means, if a file would be updated (for example: renamed) and/or scanned by MediaScannerConnection your changes will be lost. This answer is right.
Using Android Q and beyond you have to first get the file
i.e
resolver.openInputStream(uri)?.use { stream -> outputFile.copyInputStreamToFile(stream) }
return outputFile.absolutePath
Helper Function
private fun File.copyInputStreamToFile(inputStream: InputStream?) {
this.outputStream().use { fileOut ->
inputStream?.copyTo(fileOut)
}
}
Then alter the metadata via a third party, I use J Audio Tagger
Then over write the old file
// From https://developer.android.com/reference/android/content/ContentProvider
// String: Access mode for the file. May be
// "r" for read-only access,
// "w" for write-only access (erasing whatever data is currently in the file),
// "wa" for write-only access to append to any existing data,
// "rw" for read and write access on any existing data, and
// "rwt" for read and write access that truncates any existing file. This value must never be null.
mContext.application.contentResolver.openOutputStream(uri, "w")?.use { stream ->
stream.write(file.readBytes())
}
This works fine when the file was created by your app
I've been updating meta data in the MediaStore through a ContentResolver, but this no longer works with Android Q (API 29). The following code gives me a warning, and the description is not updated:
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "Some text");
res = getContext().getContentResolver().update(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values,
MediaStore.Images.Media._ID + "= ?", new String[]{sImageId});
android.process.media W/MediaProvider: Ignoring mutation of
description from com.example.android.someapp.app
This Medium post describes how Google has changed the API for accessing and updating files, but what about updating just the meta data? The warning seems to tell me Google no longer wants to allow third party apps to use the MediaStore, and I also found where the warning comes from: 
https://android.googlesource.com/platform/packages/providers/MediaProvider/+/master/src/com/android/providers/media/MediaProvider.java#2960

Performing Album Art Checks on an Audio file

I've been attempting to look for a solution with regards to the problem as stated in the title above but to no avail.
I'm trying to find a way to check if an Audio file has or does not have an album art, so in the case when there is no album art, I'll be able to set my own drawable for it.
Here's the code I'm running to initialize my song list.
ContentResolver cr = getActivity().getContentResolver();
Uri songsUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String songsSelection = MediaStore.Audio.Media.IS_MUSIC + "!= 0";
String songsSortOrder = MediaStore.Audio.Media.TITLE + " ASC";
Cursor songCur = cr.query(songsUri, null, songsSelection, null, songsSortOrder);
int songCount = 0;
if(songCur != null)
{
songCount = songCur.getCount();
if(songCount > 0)
{
while(songCur.moveToNext())
{
//String data = songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.DATA));
// Debug
//Log.e("Song Path", data);
// (long _id, long _albumId, long _artistId, String _title,
// String _artistName, String _albumName, int _duration)
//Log.e("Music ID", songCur.getString(cur.getColumnIndex(MediaStore.Audio.Media._ID)));
//Log.e("Music Album ID", songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)));
//Log.e("Music Artist ID", songCur.getString(cur.getColumnIndex(MediaStore.Audio.Media.ARTIST_ID)));
//Log.e("Music Title", songCur.getString(cur.getColumnIndex(MediaStore.Audio.Media.TITLE)));
//Log.e("Music Artist Name", songCur.getString(cur.getColumnIndex(MediaStore.Audio.Media.ARTIST)));
//Log.e("Music Album Name", songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.ALBUM)));
//Log.e("Music Duration", songCur.getString(cur.getColumnIndex(MediaStore.Audio.Media.DURATION)));
mediaManager.songFiles.add(new Song(
songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.DATA)),
songCur.getLong(songCur.getColumnIndex(MediaStore.Audio.Media._ID)),
songCur.getLong(songCur.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)),
songCur.getLong(songCur.getColumnIndex(MediaStore.Audio.Media.ARTIST_ID)),
songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.TITLE)),
songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.ARTIST)),
songCur.getString(songCur.getColumnIndex(MediaStore.Audio.Media.ALBUM)),
songCur.getInt(songCur.getColumnIndex(MediaStore.Audio.Media.DURATION))));
}
}
songCur.close();
}
Let me know if you require any further explanation on my question.
I noticed in your code that you're getting retrieving the album_id of a particular song so a while back I discovered a simple way to fetch album art by appending the album_id to a particular URI, checkout the following code
public Uri getAlbumArtUri(long albumID) {
return ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumID);
}
I then use an Image Loading library like Universal Image Loader or Glide to load the image into an ImageView, like so:
ImageLoader.getInstance().displayImage
(getAlbumArtUri(song.albumId).toString(),
holder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).
showImageOnFail(R.drawable.albumart_mp_unknown).resetViewBeforeLoading(true).
build());
showImageOnFail() takes in a drawable that would be displayed incase of error.(This means the song doesn't have album artwork)
P.S I also found the solution discussed on this Stack Overflow Post Quite useful:
Most robust way to fetch Album Art in Android
Hope this helps!

Media Album Artist Playlist Intent

In my Android application, I want the user to be able to select Albums, Artists and Playlists and then send an intent for their default media player to play them.
I fetch the ID in the standard way:
final Uri exAudioUri = Uri.parse("content://com.google.android.music.MusicContent/playlists");
final String[] proj = { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME };
final Cursor musiccursor = ctx.getContentResolver().query(exAudioUri, proj, null, null, null);
String name = null;
String id = null;
if (musiccursor != null && musiccursor.getCount() > 0) {
while (musiccursor.moveToNext()) {
name = musiccursor.getString(musiccursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME));
id = musiccursor.getString(musiccursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID));
For Playlists I'm using the following intent:
Intent playMediaIntent = new Intent(Intent.ACTION_VIEW);
playMediaIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
playMediaIntent.setType(MediaStore.Audio.Playlists.CONTENT_TYPE);
playMediaIntent.putExtra("playlist", playlistID);
The Google Play Music app intercepts this and plays the playlist successfully.
Trying to do the same for albums gives me an activity not found exception:
Intent playMediaIntent = new Intent(Intent.ACTION_VIEW);
playMediaIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//playMediaIntent.setType("vnd.android.cursor.dir/track");
playMediaIntent.setType(MediaStore.Audio.Albums.CONTENT_TYPE);
//playMediaIntent.setData(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
playMediaIntent.putExtra("album", albumID);
I know these are undocumented methods and subject to change, but I don't want to have to implement an entire media player of my own, when a user will already have chosen their favourite....
Can anyone suggest any 'standard' code that media players will intercept?
Alternatively, is there a way to get all of the songs from the album (or artist) and send an intent with data containing an array of track URIs to play or queue up??
Been to the depths of Google/Stackoverflow on this one and drawn a blank...
Please help! I thank you in advance.

Android's Media Scanner: How do I remove files?

I'm writing an app that removes files that may or may not be listed in any one of the types of media libraries such as music or pictures. While I can use the MediaScannerConnection.scanFile method to add files to the media library there doesn't seem to be any call to notify the service that the file has been removed. Sending it the path of the file that no longer exists doesn't result in the desired behavior either. How should I go about removing items from the library that no longer exist on the Android storage?
I was able to put a method together using bits and pieces from these two questions
What is the String 'volumeName' argument of MediaStore.Audio.Playlists.Members.getContentUri referring to?
How can I refresh MediaStore on Android?
Basically I just run a query on each one of the MediaStore types (Audio, Video and Images) selecting by path and deleting any records I find.
public static void RemoveAllForPaths(String[] paths, Context context)
{
private static final String[] FIELDS = { MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.TITLE };
if(paths == null || paths.length == 0) return;
String select = "";
for(String path : paths)
{
if(!select.equals("")) select += " OR ";
select += MediaStore.MediaColumns.DATA + "=?";
}
Uri uri;
Cursor ca;
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ca = context.getContentResolver().query(uri, FIELDS, select, paths, null);
for(ca.moveToFirst(); !ca.isAfterLast(); ca.moveToNext()){
int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
context.getContentResolver().delete(uri, null, null);
}
ca.close();
// More of the same just setting the URI to Video and Images
}
I'm not entirely sure how safe this is to do but it's the only solution I've found so far and some initial testing seems to be working. I invite others to submit other answers if anyone has any further information on this approach or a better method for performing this functionality.
Answer of Spencer Ruport is right, but you don't need to query and open a cursor in order to delete.
So for one file that is music file the code is simple like that:
public void DeleteMP3FromMediaStore( Context context, String path )
{
Uri rootUri = MediaStore.Audio.Media.getContentUriForPath( path );
context.getContentResolver().delete( rootUri,
MediaStore.MediaColumns.DATA + "=?", new String[]{ path } );
}
P.S. I wanted to comment answer of Spencer Ruport but don't have enough reputation yet.
Easy as pie: whenever you add a file, let MediaStore ContentProvider knows about it using
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(fileToAddInMediaStore)));
For deletion: just use
getContentResolver().delete(Uri.fromFile(fileToDeleteFromMediaStore), null, null)
The following works well for me. You can delete or add files using this.
MediaScannerConnection.scanFile(
context,
new String[]{fileToDelete, fileToAdd},
null, null);
The available method is to remove the item from library.
This post is detailed expressed how to add into or remove from the Media Library.
http://androidyue.github.io/blog/2014/01/19/scan-media-files-in-android/ Hopes this could help you.

Get Selected Image File Location in Android

I am currently making an app which works with images. I need to implement functionality where the user picks a file stored on the SD card. Once they pick the picture (using the Android gallery), the the file-location of the image will be sent to another Activity, where other work will be done upon it.
I have seen similar posts here on SO, but none to answer my question specifically. Basically this is the code I am doing when the user clicks the "Load a Picture" button:
// Create a new Intent to open the picture selector:
Intent loadPicture = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
// To start it, run the startActivityForResult() method:
startActivityForResult(loadPicture, SELECT_IMAGE);
From that code, I then have a onActivityResult() method to listen to the call-back:
// If the user tried to select an image:
if(requestCode == SELECT_IMAGE)
{
// Check if the user actually selected an image:
if(resultCode == Activity.RESULT_OK)
{
// This gets the URI of the image the user selected:
Uri selectedImage = data.getData();
// Create a new Intent to send to the next Activity:
Intent i = new Intent(currentActivty.this, nextActivity.class);
// ----------------- Problem Area -----------------
// I would like to send the filename to the Intent object, and send it over.
// However, the selectedImage.toString() method will return a
// "content://" string instead of a file location. How do I get a file
// location from that URI object?
i.putExtra("PICTURE_LOCATION", selectedImage.toString());
// Start the activity outlined with the Intent above:
startActivity(i);
As the code above states, the uri.toString() will return a content:// string instead of the file location of the selected picture. How do I obtain the file location?
Note: Another possible solution is to send over the content:// string and convert that into a Bitmap (which is what happens in the next Activity). However, I don't know how to do that.
I have found the answer to my own question. After doing some more searching, I finally stumbled upon a post here on SO which asks the same question here: android get real path by Uri.getPath().
Unfortunately, the answer has a broken link. After some Google searching, I found the correct link to the site here: http://www.androidsnippets.org/snippets/130/ (I have verified that this code does indeed work.)
However, I decided to take a different route. Since my next Activity is using an ImageView to display the picture, I am instead going to use the Uri content string for all methods that link to the next Activity.
In the next Activity, I am using the ImageView.setImageUri() method.
Here is the code I am doing in the next Activity to display the picture from the content:// string:
// Get the content string from the previous Activity:
picLocation = getIntent().getStringExtra("PICTURE_LOCATION");
// Instantiate the ImageView object:
ImageView imageViewer = (ImageView)findViewById(R.id.ImageViewer);
// Convert the Uri string into a usable Uri:
Uri temp = Uri.parse(picLocation);
imageViewer.setImageURI(temp);
I hope that this question and answer will be helpful to future Android developers.
Here's another answer that I hope someone finds useful:
You can do this for any content in the MediaStore. In my app, I have to get the path from URIs and get the URI from paths. The former:
/**
* Gets the corresponding path to a file from the given content:// URI
* #param selectedVideoUri The content:// URI to find the file path from
* #param contentResolver The content resolver to use to perform the query.
* #return the file path as a string
*/
private String getFilePathFromContentUri(Uri selectedVideoUri,
ContentResolver contentResolver) {
String filePath;
String[] filePathColumn = {MediaColumns.DATA};
Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
The latter (which I do for videos, but can also be used for Audio or Files or other types of stored content by substituting MediaStore.Audio (etc) for MediaStore.Video:
/**
* Gets the MediaStore video ID of a given file on external storage
* #param filePath The path (on external storage) of the file to resolve the ID of
* #param contentResolver The content resolver to use to perform the query.
* #return the video ID as a long
*/
private long getVideoIdFromFilePath(String filePath,
ContentResolver contentResolver) {
long videoId;
Log.d(TAG,"Loading file " + filePath);
// This returns us content://media/external/videos/media (or something like that)
// I pass in "external" because that's the MediaStore's name for the external
// storage on my device (the other possibility is "internal")
Uri videosUri = MediaStore.Video.Media.getContentUri("external");
Log.d(TAG,"videosUri = " + videosUri.toString());
String[] projection = {MediaStore.Video.VideoColumns._ID};
// TODO This will break if we have no matching item in the MediaStore.
Cursor cursor = contentResolver.query(videosUri, projection, MediaStore.Video.VideoColumns.DATA + " LIKE ?", new String[] { filePath }, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(projection[0]);
videoId = cursor.getLong(columnIndex);
Log.d(TAG,"Video ID is " + videoId);
cursor.close();
return videoId;
}
Basically, the DATA column of MediaStore (or whichever sub-section of it you're querying) stores the file path, so you use what you know to look up the DATA, or you query on the DATA field to select the content you care about.

Categories

Resources