Thursday, June 23, 2011

How to query for music on Android



Within my simple music alarm application, I needed a way to retrieve all the artists, albums and songs on the phone. Unsure of whether there was a service there already to do so, I searched about Google how to do this. The lack of any complete A-Z articles on the subject led me to write this...

In Android, all of our music information (song title, track number, album art, artist name, etc.) is stored in a system music database. There are several tables (genres, artists, playlists, audio and more) so it is important to know what you want to find before you can think of where to look. We can query these tables through the ContextResolver object found in our Context object (of an Activity, Service, BroadcastReceiver, etc.).

I have created a small example to query our SD card for all the songs, artists and albums found on it.

Download the Source Code!!

Not sure how to put music on the SD card in the Android emulator? Check out my previous blog post to learn how to do so.

Below is a test method I wrote to collect the names of every album stored on the phone. Let's look at the code and the comments. I'll explain what's remaining afterwards.

public void collectAllAlbums()
{
// the cursor we will use to iterate over the db results
Cursor cursor = null;

// the list of columns our search relates to
final String[] projection = new String[] {MediaStore.Audio.AlbumColumns.ALBUM };

// how we want to sort our results
final String sortOrder = MediaStore.Audio.AlbumColumns.ALBUM + " COLLATE LOCALIZED ASC";
try
{
// the uri of the table that we want to query
Uri uri = android.provider.MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;

// we now launch the query (be sure not to do this in the UI thread should it take a while)
cursor = mainContext.getContentResolver().query(uri, projection, null, null, sortOrder);
if (cursor != null)
{
int i = 0;
allAlbums = new String[cursor.getCount()];
cursor.moveToFirst();
while (!cursor.isAfterLast())
{
// get the 1st col in our returned data set (AlbumColumns.ALBUM)
allAlbums[i++] = cursor.getString(0);
cursor.moveToNext();
}
}
}
finally
{
if (cursor != null)
{
cursor.close();
}
}
}

So, what just happened?
  • We query the data table located at the Uri provided for all the albums in the table. All the important stuff is in ContextResolver.query(...). Check out the Android documentation on the subject for details.
  • We use a cursor to iterate over the data set returned. The cursor uses resources so you must always close the cursor when you are done with it (hence the try/finally block). As of API 11 (Android 3.0 - Honeycomb), using a CursorLoader will take care of deactivating the Cursor when the activity closes. However, if not using a CursorLoader, it is wise to close the Cursor when done with it.
  • We call getString(0) on the cursor. This will return the value of a given column in the current data row. You must know which type you are expecting, because there is getString, getLong, getDouble, etc..
Queries may take a while. It 's a pretty good idea to launch them in a background thread and once the query is done, post it back to the UI thread to update what it needs to. I do just that in the example I've included.

You can also check in my example code how long the queries take. You should not use null as your projection string in the query. The String[] projection declares which columns of the table you want returned. Passing null will return all the columns, which is very inefficient (unless that is what you want to do). Play with the example and see for yourself.





The last thing I should add: sometimes you'll have to use other data tables to find the information you're looking for. I know that in the Audio table Columns, you won't find the genre of the song unfortunately. This information is stored in a separate table, but querying its table is just the same as in this example. Use the correct Uri, know which columns you want returned (build the String[] projection), build the selection (sql WHERE) string accordingly and the ordering string (sql ORDER BY).

Happy querying!

Friday, June 17, 2011

How to mount an sdcard in the Android Emulator


Ahoy ahoy,

I've been working on an Android application. The idea was pretty simple enough: an alarm that plays a random song each time it goes off. It being my first venture into Android and developing with Java (no, I was not using Monodroid to do it in .NET... at least not yet), I was bound to run into a few hurdles along the way.

One of these problems I ran into was how to make the emulator think that it has an sdcard installed so that I could query it for all the music that it contains. There are a few articles spread out on blogs and IT sites on the topic but I found nothing that was comprehensive and complete enough to allow me to learn the basics on it and get it done quickly. The goal of this article is just that.

I create a test project you can run in Eclipse to verify that you've indeed got your SD card properly installed on the emulator. It is simple enough: a ListActivity that lists the title of all songs found on the SD card.

You can download it HERE!


1. Create an SD card
The android SDK comes with a tool to create an SD card file to be used with the emulator. The tool is mksdcard. To use the tool:
  • open a command prompt (Windows Key + R, type cmd, hit Enter)
  • go to the directory where the android sdk was installed (mine is C:\Java\android-sdk-windows\tools)
  • mksdcard 512M C:/Users/Sean/Workspace/emulator_sdcard
  • Wait a few seconds (the bigger the sd card size, the much longer you'll wait...)
  • Verify that the file was indeed created in the desired location

2. Assign the SD card to the emulator
This is done inside of Eclipse.
  • In the top menu, go to Run > Debug Configurations...
  • Choose your debug configuration in the treeview on the left. You may have to create one if you have not debugged the project before.
  • Select the Target tab.
  • At the bottom of the options is a field. Additional Emulator Command Line Options
  • Enter -sdcard your_sd_card_location (mine was C:/Users/Sean/Workspace/emulator_sdcard)
  • Hit the Apply button at the bottom. Then close the dialog.
  • If the emulator is already open, close it.
  • Now debug your project. (Right-click on the project > Debug As > Android Application





If we re-run our test application, there is still nothing of interest. We must now fill the SD card with useful data in order for it to be read by our killer app.


3. Push data onto the SD card
To accomplish this we will use a graphical tool included in the android sdk and found in Eclipse. If you stopped the debugger from the previous step, go ahead and relaunch it.
  • In Eclipse from the main menu, go to Window > Show View> File Explorer. Or you could choose the DDMS View already inside Eclipse. It contains the File Explorer.
  • Once inside the File Explorer, you'll find the "Push a file onto the device" button in the menu bar. But beware, you can't add your data files just anywhere on the SD card. Many system directories are read-only.
  • Go into the mnt/sdcard folder and add your data there. You can do so by:
- The "Push a file onto the device" menu item
- Drag and drop the file onto the selected folder
- Through adb (the command-line tool) with the following command:
adb push [C:\music\example.mp3] /sdcard/[example.mp3]
adb push [C:\music\myFavoriteAlbumDirectory] /sdcard/[directoryName]
(adb is located in the android-sdk installation directory. Mine was C:\Java\android- sdk-windows\tools)

You should now see your file on the SD card in the file explorer.

Hey, wait a minute! It's not there, something went wrong.
It's true, we don't live in a perfect world and yes, things can go askew sometimes. If your file is not there, look in the output console in Eclipse, it can help us find out why. Some possible causes/remedies:
  • You tried adding your file in a read-only directory. Push the file into mnt/sdcard to be sure.
  • You may need to restart Eclipse (it worked for me when I got this message in the console:
[2011-06-18 10:30:17] Failed to push 07. That's The Way Love Is.mp3 on emulator-5554: null
  • Your SD card is full. Unfortunately if this is the case, you cannot resize the SD card file you created initially. You'll have to recreate it with mksdcard but with a bigger size this time.
If you want to create a directory structure on the SD card, you cannot use the File Explorer in Eclipse. Instead use a file browser app on the emulator or adb (same command used to push a file but use it on a directory instead... but not an empty directory... that won't work).


4. Relaunch the app
At this point we will see our list of music on the sdcard in our app. Close the emulator and debug the application once more. We should now see a list of the songs (we obtained through legal means...) we added to the SD card.


Enjoy,
Sean