Run MediaStore queries on background thread
This commit is contained in:
parent
95a7a32e35
commit
690789273b
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
61
src/org/kreed/vanilla/QueryTask.java
Normal file
61
src/org/kreed/vanilla/QueryTask.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user