I'm creating a music player that populates an array list with song objects. Each song object has a series of attributes. One of those attributes is the album-art URI.
This is the line of code where I assign the URI attribute to the song object.
songObject.albumArtURI = GetAlbumArtURI(albumID);
Here is the string value of albumID
Log.v("TAG",String.valueOf(albumID)); // prints [Ljava.lang.String;#44ce53d
When I pass albumID to GetAlbumArtURI() method
private String GetAlbumArtURI(String[] albumID){
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums.ALBUM_ID + "=?",
albumID, // Error
null
);
return mCursor.getString(0);
}
I get this error:
no such column: album_id (code 1)
while compiling:
SELECT album_art FROM album_info WHERE (album_id=?)
The error essentially says that table album_info does not contain album_id column. But according to the documenation, album_info does have such a column.
So there's a few issues causing your query to return nothing and throw that error.
MediaStore.Audio.Albums.ALBUM_ID is not the column you want to reference. You should be using MediaStore.Audio.Albums._ID.
You need to move your cursor's read position to the first position when you get results, if possible. Doing otherwise will result in you never getting the results you need
The way that MediaStore works on android is that you have to register the media files that you want the OS to know about - this isn't automatic. You need to implement something similar to the SingleMediaScanner described in this thread
Here is the working bit of code that I have written:
try {
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums._ID + "=?",
null,
null
);
// You need to check if there are results in your cursor.
// This is like saying if(mCursor.getCount() > 0)
if(mCursor.moveToFirst()) {
return mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
} else {
return "";
}
} catch (Exception e) {
Log.e(this.getClass().getSimpleName(),e.getMessage());
}
When you have called that code, you're assuming that the MediaStore on your device knows about the music files you've downloaded or added. You can definitely implement a BroadcastReceiver to capture system events like files being added, but for this answer I'm just going to show how you account for one known file. You could also expand this to search an entire directory by adding to the onMediaScannerConnected(...) method.
If you implement the SingleMediaScanner file found here you can then just do:
File file = new File(Environment.getExternalStorageDirectory() + "/Download/1.mp3");
SingleMediaScanner singleMediaScanner = new SingleMediaScanner(this, file);
And it will register the media file in your MediaStore. At that point, you should be able to get results back from your query above. If you are having doubts of whether or not the songs are being registered, you can check to see if any records have been added at all by changing your mCursor call to this (to get all the results in the media store) and then iterating through them:
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
null,
null,
null,
null
);
Related
I am new to contenProvider and am reading a tutorial on how to do CRUD operations on UserDictionary.
For Query:
resolver.query(UserDictionary.Words.CONTENT_URI, projection, null, null, null);
For insert:
resolver.insert(UserDictionary.Words.CONTENT_URI, values); //ContentValues values = new ContentValues();
For update:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
long noUpdated = resolver.update(uri, values, null, null);
for delete:
long noDeleted = resolver.delete(Words.CONTENT_URI,
Words.WORD + " = ? ", new String[]{"Zaphod"});
My confusion is in update and delete operations.
In update: why is it using Words.CONTENT_URI in withAppendedId() ? shouldn't it be using UserDictionary.Words.CONTENT_URI ?
Also in delete: its not using withAppendedId(). Still why is it using Words.CONTENT_URI ?
In your example UserDictionary.Words.CONTENT_URI is what identifies the data in a provider, so the data location, the appended Id is what identifies a unique record.
So, by your examples:
1) For Query, is using UserDictionary.Words.CONTENT_URI to return all the records under this content Uri address. If you wanted to return only one specific record, from which you already know its Id, then it could also be as follows:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
resolver.query(uri, projection, null, null, null);
3) For Insert, no Id is used because the record doesn't exist yet. What the operation does is to insert a new record into the database, and the return value will be the new record Uri, which will include the new Id.
3) For Update, the appended Id is used to identify which specific record must be updated. Updating UserDictionary.Words.CONTENT_URI without a record Id would result in the update of all the records.
4) For Delete, your example is deleting all the records where the column Words.WORD has the value "Zaphod", which may result in 0, 1, or multiple records being deleted.
If you wanted to delete only 1 specific record from which you know its Id, then it would be as next:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
resolver.delete(uri, null, null);
Answering your last question, there is no difference between Words.CONTENT_URI and UserDictionary.Words.CONTENT_URI. The UserDictionary section is a qualifier, which can be removed if has been added as an import on top of the java file, this allows not having to type it all the time, but at the end both are actually the same thing and makes no difference.
so I'm pretty new to Android development. I'm trying to have the user select a song from their SD card or internal storage using a file manager and upload the file to a server. Right now, I'm just trying to access the name of the file that the user selected so that I can use it to access the contents of the file later and upload those contents.
I've looked at other posts concerning this and most of them tell you to query the content resolver and then use the cursor to grab the display name, but that does not always return the display name (this is mentioned in Google's guide to the SAF). It has not been returning the full file name, just part of it (the title of the song).
Here's my code that starts the intent:
Intent chooseIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooseIntent.addCategory(Intent.CATEGORY_OPENABLE);
chooseIntent.setType("audio/*");
startActivityForResult(chooseIntent, SELECT_SONG_FILE_REQUEST_CODE);
How I'm getting the display name now:
String fileName = null;
if (uri.getScheme().equals("content")) {
try (Cursor cursor = getContentResolver().query(uri, null, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
fileName = cursor.getString(
cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "Filename: " + fileName);
cursor.close();
}
}
}
How can I get the full file name rather than just the display name?
You also can use TITLE in Cursor as URI parameter to get the full name of file
use below code it
Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.TITLE, null, null, null);
Or You can use DATA to get Path of the file
Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.DATA, null, null, null);
All You have to do is let the user choose the music and after selecting with the use of DATA you have full path of song and with TITLE you have full name of file .
Next just upload it.
There are several things that you can do to improve the performance of the query
You are getting all the columns which can be painfully slow.
You can write code like the one below and then check what field you want to use. You will get all the data for all the audio files installed in the device. You can walk through this code in the debugger to see the data that you need.
{
Uri objUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ContentResolver cr = context.getContentResolver();
String[] entityColumns = {MediaStore.Audio.AudioColumns.DATA,
MediaStore.Audio.AudioColumns.DISPLAY_NAME,
MediaStore.Audio.AudioColumns.TITLE};
cursor = cr.query(
objUri,
entityColumns,
null,
null,
null);
if (cursor != null && cursor.moveToFirst()) {
do {
String filePath = cursor.getString(cursor.getColumnIndex(entityCoumns[0]));
String fileName = cursor.getString(cursor.getColumnIndex(entityColumns[1]));
String fileTitle =
cursor.getString(cursor.getColumnIndex(entityColumns[2]));
} while (cursor.moveToNext());
cursor.close();
}
}
I want to prevent my music player app from scanning the directories for audio files everytime the app launches. How can I do that?
I have been using the following code to scan the audio files.
public void getSongList() {
ContentResolver contentResolver=getContentResolver();
Uri musicUri=android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = contentResolver.query(musicUri, null, null, null, null);
if(musicCursor!=null && musicCursor.moveToFirst()) {
//get columns
int titleColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media._ID);
int artistColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.ARTIST);
int albumIDColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
//add songs to list
do {
long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
long thisAlbumID=musicCursor.getLong(albumIDColumn);
Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Bitmap albumArtBitMap=null;
Uri albumArtUri = ContentUris.withAppendedId(sArtworkUri, thisAlbumID);
try {
albumArtBitMap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), albumArtUri);
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, albumArtBitMap.getWidth(), albumArtBitMap.getHeight()), new RectF(0, 0, 300, 300), Matrix.ScaleToFit.CENTER);
albumArtBitMap = Bitmap.createBitmap(albumArtBitMap, 0, 0, albumArtBitMap.getWidth(), albumArtBitMap.getHeight(), m, true);
} catch (IOException e) {
e.printStackTrace();
}
songList.add(new Song(thisId, thisTitle, thisArtist,albumArtBitMap));
}
while (musicCursor.moveToNext());
}
}
I want the app to only scan when there are new files. Because If I scan the whole SD card every time then it'll take too much time for starting the app. Please Help me with that
No need to keep all the songs in local app list.
To show the mp3list you can use content provider cursor list adapter query with limit (on scroll query page by page )
To search use directly the contentprovider query method.
Only keep a playslist on you local database pointing to mp3 uri.
this link might helps you :
How to update listview whose data was queried from database through SimpleCursorAdapter?
Every time you start the app see how much space you have on your device.
File path = Environment.getDataDirectory();
megaBytesAvailable(path)
public static float megaBytesAvailable(File file) {
StatFs stat = new StatFs(file.getPath());
long bytesAvailable = (long)stat.getBlockSizeLong() * (long)stat.getAvailableBlocksLong();
return bytesAvailable / (1024.f * 1024.f);
}
Save it to your app's cache as a variable and compare it every time you start the app, if it's greater then you know you need to scan.
if(comparedVariable < megaBytesAvailable(music_directory_path)){
getSongList();
//Save it again to compare next time if more storage is used
comparedVariable = megaBytesAvailable(music_directory_path);
//Save it to SharedPrefs for next boot up comparison
}
I think #royrok answer can help you, where #royrok checking the playlist in mediastore instead rescan the sdcard. Below I include #royrok answer.
Rather than rescan the card, the app iterates through all the playlists in MediaStore and checks the length of the _data field. I discovered that for all the lists with no associated M3U file, this field was always empty. Then it was just a case of finding the source code for the original android music app, finding the delete method and using that to delete any playlists with a length of 0. I've renamed the app PlaylistPurge (since it doesn't 'rescan' anymore) and am posting the code below:
package com.roryok.PlaylistPurge;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
public class PlaylistPurge extends ListActivity {
private List<String> list = new ArrayList<String>();
private final String [] STAR= {"*"};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListAdapter adapter = createAdapter();
setListAdapter(adapter);
}
/**
* Creates and returns a list adapter for the current list activity
* #return
*/
protected ListAdapter createAdapter()
{
// return play-lists
Uri playlist_uri= MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
Cursor cursor= managedQuery(playlist_uri, STAR, null,null,null);
cursor.moveToFirst();
for(int r= 0; r<cursor.getCount(); r++, cursor.moveToNext()){
int i = cursor.getInt(0);
int l = cursor.getString(1).length();
if(l>0){
// keep any playlists with a valid data field, and let me know
list.add("Keeping : " + cursor.getString(2) + " : id(" + i + ")");
}else{
// delete any play-lists with a data length of '0'
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, i);
getContentResolver().delete(uri, null, null);
list.add("Deleted : " + cursor.getString(2) + " : id(" + i + ")");
}
}
cursor.close();
// publish list of retained / deleted playlists
ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
return adapter;
}
}
Here's a link to a post on my blog about the app http://roryok.com/blog/index.php/2010/07/23/clearing-out-deleted-playlists-in-android/
UPDATE
I've found an article about Querying And Removing Media From The Android MediaStore, I included the content below.
.
.
.
Android provides a way to register different type of media, such as audio, video, and images, for consumption by any app. This is convenient if your app is, say, a music player or an image editor. Android's MediaStore is the provider for this meta data, and includes information about the media such as title, artist, size, and location.
If your application does any sort of media content creation, such as image editing or downloading audio from an external website, then you generally want to make that content accessible from any other apps that can consume it. When you create a file you can use the MediaScannerConnection to add the file and its metadata to the MediaStore.
If you delete the file from the file system, the metadata remains in the MediaStore until Android scans the system for new media, which typically happens when the system first boots up or can be called explicitly called in such a way:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + Environment.getExternalStorageDirectory() )));
While this method works, it is time and resource consuming, as basically the entire file system must be re-scanned. An alternative is to explicitly delete the file from the MediaStore. We're going to discuss two ways to do this. The first is to query to MediaStore for the content, based on some predicate, and delete based on the unique ID the MediaStore identifies it by. The second, and easier, way to do it is to just specify the predicate in the delete statement. In this example, I'm going to be deleting an audio file based on its file name and path, but you can easily use this to delete any type of media based on any known information (such as video duration, or image dimensions).
In querying the MediaStore, you should think of it as an SQL database. You need to form your query by specifying the table (the MediaStore's external content table), the columns you need (the content’s ID), and the where clause (how to identify the content). To perform the actual query, we’re going to use the ContentResolver's query() method.
String[] retCol = { MediaStore.Audio.Media._ID };
Cursor cur = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
retCol,
MediaStore.MediaColumns.DATA + "='" + filePath + "'", null, null);
if (cur.getCount() == 0) {
return;
}
cur.moveToFirst();
int id = cur.getInt(cur.getColumnIndex(MediaStore.MediaColumns._ID));
cur.close();
The first argument to query() specifies the columns we want to be returned, which in this case is only "_ID". The second argument specifies that we want to look at the media stored on the external SD card (which would be internal storage on devices with no SD card). The third argument is the predicate which specifies what content we're looking for. In this case, I'm identifying the file by its path in the file system (which is what is stored in the MediaColumns.DATA column). The fourth and fifth columns are the predicate's arguments and the ordering, respectively. I'm including my predicate's arguments in the predicate itself so that's not necessary, and if your only looking for one piece of content and your predicate is specific enough to just return one row then the ordering doesn't matter.
It is very important to make the predicate specific enough so that you're guaranteed to get the exact ID you're looking for. In my case, I know that there can be only one file at a particular location, but you could use a combination of any columns (such as title, artist, and album) to find the content. Check out the MediaColumns for all the possibilities.
Once you perform the actual query, you'll want to check to see whether the MediaStore actually contains the content you're trying to delete. If you don't handle this in some way your app will crash while trying to iterate through the cursor. Once you confirm that the query returned some data, grab the ID by moving the cursor to its first position, reading the “_ID” column, and closing the cursor. It's very important that you remember to close the cursor once you've finished using it. Your app won't crash, but you'll get memory leaks and complaints in LogCat.
Now that we have the ID that the MediaStore associated with our content, we can call ContentResolver's delete() method similar to how we called its query() method.
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
id);
context.getContentResolver().delete(uri, null, null);
The delete() method takes 3 arguments: the Uri to be deleted, the predicate, and the predicate arguments. We form the Uri by appending the ID we discovered by querying the MediaStore to the Uri of the audio files on external storage. Since we know exactly which row we want to delete, we don't need to specify the predicate or the predicate's arguments.
The second method to delete the content from the MediaStore takes advantage of the fact that querying and deleting from it are performed almost identically.
context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
MediaStore.MediaColumns.DATA + "='" + path + "'", null);
We can use the predicate of the delete() method to specify exactly what we want to delete, rather than having to query for it beforehand. While this method is more efficient (no extra query, no cursors to deal with), it has some pitfalls. You have no way of explicitly confirming what you're deleting. You're also not able to do advanced queries with this method, such as if you wanted to delete the most recently added content (which you could do by ordering the query based on the DATE_ADDED column). However, both ways give you a way to confirm what you've deleted since the delete() method returns the number of rows that it deleted as an integer.
I have videoplayer app with filebrowser listing all videos on SD card
Code inspired by i want get audio files in sd card
Using ContentResolver, works as expected, but it does not update if the files on card change. I do not mean automatically, but after view/app restart. Not even reinstalling the application helped, still shows the same files. The deleted video file is not visible via PC nor it is possible to play it (This video cannot be played (translation)).
I dumped the data and the problem is not in view caching or elsewhere. I do not implement any caching of my own and failed to find anything on the matter. Thank you
Code:
// acquisition
String[] projection = {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.DATA
};
ContentResolver resolver = getActivity().getContentResolver();
Cursor videoCursor = resolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
null
);
// extraction
while(cursor.moveToNext()) {
cursorIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
filepath = cursor.getString(cursorIndex);
cursorIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
filename = cursor.getString(cursorIndex);
cursorIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
duration = cursor.getString(cursorIndex);
result[ index++ ] = new VideoFileMetadata(filename, duration, filepath);
}
Edit 1 [14-03-2013]:
I tried adding number + " = " + number to ORDER or WHERE clause to act as a potential query caching buster, but it had no effect (although it's possible it was removed by an optimizer as a useless clause). This time I had reinstalled the application from a different machine using different certificate, but the query result remained the same, listing currently non-existing files.
You should first call cursor.moveToFirst() .
So, your cursor iteration loop should look like
if (cursor.moveToFirst()) {
do {
// cursorIndex = cursor.getColumnIndexOrThrow, etc...
} while (cursor.moveToNext());
}
Got a problem here with a simple section of code that uses managedQuery cursors. Two parts, the top half of the code puts a string into the LATITUDE column of the MediaStore database content provider.
The second part of the code below that reads that same string back from the database. This is where it is returning a null result. Either because the string was not correctly read into the database in the first part of the code or there is an error in the second part where it reads it back from the database.
I am using the LATITUDE column of the Media.images content provider to store a string. There is no other unused column that is available so that is why I am using it.
The goal is to put the string path name of the mp3 file into the LATITUDE column of an image and read it back out again later with another query.
I tracked down the problem to the following code. The cursor in the second part of code is returning null. Is there something wrong with my use of cursors, or some error in this that I don't know about?
String displayName; // string pathname of the mp3 file to be put into LATITUDE column
String filename; // the pathname of the image that I want to add the database info to
ContentValues imageValues = new ContentValues();
String selection3 = MediaStore.Images.Media.DATA + "='" + filename +"'";
imageValues.put(MediaStore.Images.Media.LATITUDE, displayName);
getContentResolver().update(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
imageValues, selection3, null);
String[] proj6 = { MediaStore.Images.Media.LATITUDE };
String selection6 = MediaStore.Images.Media.DATA + "='" + filename +"'";
Cursor cursor2 = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
proj6, selection6, null, null);
cursor2.moveToFirst();
String displayer = (String)
cursor2.getString(cursor2.getColumnIndex(MediaStore.Images.Media.LATITUDE));
Found out that there was not an error like I thought it would be. It was only a typing error.
put method has "Images.Media.LATITUDE"
update method has "Audio.Media.LATITUDE", changed this to "Images.Media.LATITUDE" like it is in the put method and it works now.
imageValues.put(MediaStore.Images.Media.LATITUDE, displayName);
getContentResolver().update(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
imageValues, selection3, null);