Run MediaStore queries on background thread

This commit is contained in:
Christopher Eby 2011-09-20 21:49:28 -05:00
parent 95a7a32e35
commit 690789273b
7 changed files with 154 additions and 92 deletions

View File

@ -228,7 +228,8 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
if (limiter != null && limiter.type == MediaUtils.TYPE_GENRE) {
// Genre is not standard metadata for MediaStore.Audio.Media.
// We have to query it through a separate provider. : /
cursor = MediaUtils.queryGenre(mActivity, mLimiter.id, projection, selection.toString(), selectionArgs);
QueryTask query = MediaUtils.buildGenreQuery(mLimiter.id, projection, selection.toString(), selectionArgs);
cursor = query.runQuery(resolver);
} else {
if (limiter != null) {
if (selection.length() != 0)

View File

@ -88,18 +88,17 @@ public class MediaUtils {
}
/**
* Return a cursor containing the ids of all the songs with artist or
* album of the specified id.
* Builds a query that will return all the songs represented by the given
* parameters.
*
* @param context A context to use.
* @param type One of the TYPE_* constants, excluding playlists.
* @param id The MediaStore id of the artist or album.
* @param type MediaUtils.TYPE_ARTIST, TYPE_ALBUM, or TYPE_SONG.
* @param id The MediaStore id of the song, artist, or album.
* @param projection The columns to query.
* @param select An extra selection to pass to the query, or null.
* @return The initialized query.
*/
private static Cursor getMediaCursor(Context context, int type, long id, String[] projection, String select)
public static QueryTask buildMediaQuery(int type, long id, String[] projection, String select)
{
ContentResolver resolver = context.getContentResolver();
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
StringBuilder selection = new StringBuilder();
@ -127,47 +126,44 @@ public class MediaUtils {
}
String sort = MediaStore.Audio.Media.ARTIST_KEY + ',' + MediaStore.Audio.Media.ALBUM_KEY + ',' + MediaStore.Audio.Media.TRACK;
return resolver.query(media, projection, selection.toString(), null, sort);
return new QueryTask(media, projection, selection.toString(), null, sort);
}
/**
* Return a cursor containing the ids of all the songs in the playlist
* with the given id.
* Builds a query that will return all the songs in the playlist with the
* given id.
*
* @param context A context to use.
* @param id The id of the playlist in MediaStore.Audio.Playlists.
* @param projection The columns to query.
* @param selection The selection to pass to the query, or null.
* @return The initialized query.
*/
private static Cursor getPlaylistCursor(Context context, long id, String[] projection)
public static QueryTask buildPlaylistQuery(long id, String[] projection, String selection)
{
ContentResolver resolver = context.getContentResolver();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", id);
String sort = MediaStore.Audio.Playlists.Members.PLAY_ORDER;
return resolver.query(uri, projection, null, null, sort);
return new QueryTask(uri, projection, selection, null, sort);
}
/**
* Return a cursor containing the ids of all the songs in the genre
* with the given id.
* Builds a query that will return all the songs in the genre with the
* given id.
*
* @param context A context to use.
* @param id The id of the genre in MediaStore.Audio.Genres.
* @param projection The columns to query.
* @param selection The selection to pass to the query, or null.
* @param selectionArgs The arguments to substitute into the selection.
*/
public static Cursor queryGenre(Context context, long id, String[] projection, String selection, String[] selectionArgs)
public static QueryTask buildGenreQuery(long id, String[] projection, String selection, String[] selectionArgs)
{
ContentResolver resolver = context.getContentResolver();
Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", id);
String sort = MediaStore.Audio.Genres.Members.TITLE_KEY;
return resolver.query(uri, projection, selection, selectionArgs, sort);
return new QueryTask(uri, projection, selection, selectionArgs, sort);
}
/**
* Returns a Cursor queried with the given information.
* Builds a query with the given information.
*
* @param context A context to use.
* @param type Type the id represents. Must be one of the Song.TYPE_*
* constants.
* @param id The id of the element in the MediaStore content provider for
@ -175,25 +171,24 @@ public class MediaUtils {
* @param selection An extra selection to be passed to the query. May be
* null. Must not be used with type == TYPE_SONG or type == TYPE_PLAYLIST
*/
public static Cursor query(Context context, int type, long id, String[] projection, String selection)
public static QueryTask buildQuery(int type, long id, String[] projection, String selection)
{
switch (type) {
case TYPE_ARTIST:
case TYPE_ALBUM:
case TYPE_SONG:
return getMediaCursor(context, type, id, projection, selection);
return buildMediaQuery(type, id, projection, selection);
case TYPE_PLAYLIST:
assert(selection == null);
return getPlaylistCursor(context, id, projection);
return buildPlaylistQuery(id, projection, selection);
case TYPE_GENRE:
return queryGenre(context, id, projection, selection, null);
return buildGenreQuery(id, projection, selection, null);
default:
throw new IllegalArgumentException("Specified type not valid: " + type);
}
}
/**
* Return an array containing all the song ids that match the specified parameters
* Return an array containing all the song ids that match the specified parameters. Should be run on a background thread.
*
* @param context A context to use.
* @param type Type the id represents. Must be one of the Song.TYPE_*
@ -206,11 +201,9 @@ public class MediaUtils {
if (type == TYPE_SONG)
return new long[] { id };
Cursor cursor;
if (type == MediaUtils.TYPE_PLAYLIST)
cursor = query(context, type, id, Song.EMPTY_PLAYLIST_PROJECTION, null);
else
cursor = query(context, type, id, Song.EMPTY_PROJECTION, null);
String[] projection = type == MediaUtils.TYPE_PLAYLIST ?
Song.EMPTY_PLAYLIST_PROJECTION : Song.EMPTY_PROJECTION;
Cursor cursor = buildQuery(type, id, projection, null).runQuery(context.getContentResolver());
if (cursor == null)
return null;
@ -230,7 +223,8 @@ public class MediaUtils {
}
/**
* Delete all the songs in the given media set.
* Delete all the songs in the given media set. Should be run on a
* background thread.
*
* @param context A context to use.
* @param type One of the TYPE_* constants, excluding playlists.
@ -243,7 +237,7 @@ public class MediaUtils {
ContentResolver resolver = context.getContentResolver();
String[] projection = new String [] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA };
Cursor cursor = getMediaCursor(context, type, id, projection, null);
Cursor cursor = buildQuery(type, id, projection, null).runQuery(resolver);
if (cursor != null) {
PlaybackService service = PlaybackService.hasInstance() ? PlaybackService.get(context) : null;

View File

@ -380,9 +380,7 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
public void enqueue(int type)
{
int count = PlaybackService.get(this).enqueueFromCurrent(type);
String text = getResources().getQuantityString(R.plurals.enqueued, count, count);
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
PlaybackService.get(this).enqueueFromCurrent(type);
}
public void performAction(int action)

View File

@ -786,6 +786,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
private static final int POST_CREATE = 1;
/**
* Run the given query and add the results to the timeline.
*
* obj is the QueryTask. arg1 is the add mode (one of SongTimeline.MODE_*)
*/
private static final int QUERY = 2;
/**
* This message is sent with a delay specified by a user preference. After
* this delay, assuming no new IDLE_TIMEOUT messages cancel it, playback
@ -825,6 +831,9 @@ public final class PlaybackService extends Service implements Handler.Callback,
case PROCESS_SONG:
processSong((Song)message.obj);
break;
case QUERY:
runQuery(message.arg1, (QueryTask)message.obj);
break;
case POST_CREATE:
mHeadsetPause = mSettings.getBoolean("headset_pause", true);
setupReceiver();
@ -970,18 +979,45 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
/**
* Add a song or group of songs represented by the given type and id to the
* timeline.
* Run the query and add the results to the timeline. Should be called in the
* worker thread.
*/
public void runQuery(int mode, QueryTask query)
{
int count = mTimeline.addSongs(mode, query.runQuery(getContentResolver()));
int text;
switch (mode) {
case SongTimeline.MODE_PLAY:
text = R.plurals.playing;
break;
case SongTimeline.MODE_PLAY_NEXT:
case SongTimeline.MODE_ENQUEUE:
text = R.plurals.enqueued;
break;
default:
return;
}
if (mode == SongTimeline.MODE_PLAY && count != 0 && (mState & FLAG_PLAYING) == 0)
setFlag(FLAG_PLAYING);
Toast.makeText(this, getResources().getQuantityString(text, count, count), Toast.LENGTH_SHORT).show();
}
/**
* Run the query in the background and add the results to the timeline.
*
* @param mode One of SongTimeline.MODE_*. Tells whether to play the songs
* immediately or enqueue them for later.
* @param type The media type, one of MediaUtils.TYPE_*
* @param id The MediaStore id of the media
* @return The number of songs that were enqueued.
* @param query The query.
*/
public int addSongs(int mode, int type, long id)
public void addSongs(int mode, QueryTask query)
{
return mTimeline.addSongs(mode, type, id, null);
Message msg = mHandler.obtainMessage(QUERY, query);
msg.arg1 = mode;
mHandler.sendMessage(msg);
}
/**
@ -993,13 +1029,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
*
* @param type The media type, one of MediaUtils.TYPE_ALBUM, TYPE_ARTIST,
* or TYPE_GENRE
* @return The number of songs that were enqueued.
*/
public int enqueueFromCurrent(int type)
public void enqueueFromCurrent(int type)
{
Song current = mCurrentSong;
if (current == null)
return 0;
return;
long id;
switch (type) {
@ -1017,7 +1052,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
}
String selection = "_id!=" + current.id;
return mTimeline.addSongs(SongTimeline.MODE_PLAY_NEXT, type, id, selection);
addSongs(SongTimeline.MODE_PLAY_NEXT, MediaUtils.buildQuery(type, id, Song.FILLED_PROJECTION, selection));
}
/**

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kreed.vanilla;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
/**
* Represents a pending query.
*/
public class QueryTask {
private Uri mUri;
private String[] mProjection;
private String mSelection;
private String[] mSelectionArgs;
private String mSortOrder;
/**
* Create the tasks. All arguments are passed directly to
* ContentResolver.query().
*/
public QueryTask(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
mUri = uri;
mProjection = projection;
mSelection = selection;
mSelectionArgs = selectionArgs;
mSortOrder = sortOrder;
}
/**
* Run the query. Should be called on a background thread.
*
* @param resolver The ContentResolver to query with.
*/
public Cursor runQuery(ContentResolver resolver)
{
return resolver.query(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder);
}
}

View File

@ -66,6 +66,8 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private static final int ACTION_PLAY = 0;
private static final int ACTION_ENQUEUE = 1;
private static final int ACTION_LAST_USED = 2;
private static final int[] modeForAction =
{ SongTimeline.MODE_PLAY, SongTimeline.MODE_ENQUEUE };
private TabHost mTabHost;
@ -244,38 +246,19 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
*/
private void pickSongs(MediaView view, int action)
{
PlaybackService service = PlaybackService.get(this);
int type = view.getMediaType();
long id = view.getMediaId();
int mode;
int text;
if (action == ACTION_LAST_USED)
action = mLastAction;
else
mLastAction = action;
switch (action) {
case ACTION_PLAY:
mode = SongTimeline.MODE_PLAY;
text = R.plurals.playing;
break;
case ACTION_ENQUEUE:
mode = SongTimeline.MODE_ENQUEUE;
text = R.plurals.enqueued;
break;
default:
return;
}
int count = service.addSongs(mode, type, id);
setSong(service.getSong(0));
if (action == ACTION_PLAY && (mState & PlaybackService.FLAG_PLAYING) == 0)
setState(service.setFlag(PlaybackService.FLAG_PLAYING));
Toast.makeText(this, getResources().getQuantityString(text, count, count), Toast.LENGTH_SHORT).show();
mLastActedId = id;
int mode = modeForAction[action];
String[] projection = type == MediaUtils.TYPE_PLAYLIST ?
Song.FILLED_PLAYLIST_PROJECTION : Song.FILLED_PROJECTION;
QueryTask query = MediaUtils.buildQuery(type, id, projection, null);
PlaybackService.get(this).addSongs(mode, query);
}
/**

View File

@ -66,19 +66,19 @@ public final class SongTimeline {
/**
* Clear the timeline and use only the provided songs.
*
* @see SongTimeline#addSongs(int,int,long,String)
* @see SongTimeline#addSongs(int,Cursor)
*/
public static final int MODE_PLAY = 0;
/**
* Clear the queue and add the songs after the current song.
*
* @see SongTimeline#addSongs(int,int,long,String)
* @see SongTimeline#addSongs(int,Cursor)
*/
public static final int MODE_PLAY_NEXT = 1;
/**
* Add the songs at the end of the timeline, clearing random songs.
*
* @see SongTimeline#addSongs(int,int,long,String)
* @see SongTimeline#addSongs(int,Cursor)
*/
public static final int MODE_ENQUEUE = 2;
@ -463,24 +463,14 @@ public final class SongTimeline {
}
/**
* Add a set of songs to the song timeline.
* Add the songs from the given cursor to the song timeline.
*
* @param mode How to add the songs. One of SongTimeline.MODE_*.
* @param type The type represented by the id. Must be one of the
* MediaUtils.FIELD_* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
* @param selection An extra selection to be passed to the query. May be
* null. Must not be used with type == TYPE_SONG or type == TYPE_PLAYLIST
* @return The number of songs that were enqueued.
* @param cursor The cursor to fill from.
* @return The number of songs that were added.
*/
public int addSongs(int mode, int type, long id, String selection)
public int addSongs(int mode, Cursor cursor)
{
Cursor cursor;
if (type == MediaUtils.TYPE_PLAYLIST)
cursor = MediaUtils.query(mContext, type, id, Song.FILLED_PLAYLIST_PROJECTION, selection);
else
cursor = MediaUtils.query(mContext, type, id, Song.FILLED_PROJECTION, selection);
if (cursor == null)
return 0;
int count = cursor.getCount();
@ -514,7 +504,7 @@ public final class SongTimeline {
int start = timeline.size();
for (int j = 0; j != count; ++j) {
cursor.moveToNext();
cursor.moveToPosition(j);
Song song = new Song(-1);
song.populate(cursor);
timeline.add(song);