Run library queries on worker thread

This commit is contained in:
Christopher Eby 2011-09-18 18:50:55 -05:00
parent e23598aef1
commit af069c208b
3 changed files with 122 additions and 95 deletions

View File

@ -43,8 +43,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.Filter;
import android.widget.FilterQueryProvider;
import java.io.Serializable;
/**
@ -58,9 +56,11 @@ import java.io.Serializable;
* to a specific group to be displayed, e.g. only songs from a certain artist.
* See MediaView.getLimiter and setLimiter for details.
*/
public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
private Context mContext;
public class MediaAdapter extends CursorAdapter {
/**
* The activity that owns this adapter.
*/
private SongSelector mActivity;
/**
* The type of media represented by this adapter. Must be one of the
* MediaUtils.FIELD_* constants. Determines which content provider to query for
@ -93,29 +93,27 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
*/
private Limiter mLimiter;
/**
* The last constraint used in a call to filter.
* The constraint used for filtering, set by the search box.
*/
private CharSequence mConstraint;
private String mConstraint;
/**
* Construct a MediaAdapter representing the given <code>type</code> of
* media.
*
* @param context A Context to use
* @param activity The activity that owns this adapter.
* @param type The type of media to represent. Must be one of the
* Song.TYPE_* constants. This determines which content provider to query
* and what fields to display in the views.
* @param expandable Whether an expand arrow should be shown to the right
* of the views' text
* @param requery If true, automatically update the adapter when the
* provider backing it changes
* @param limiter An initial limiter to use
*/
public MediaAdapter(Context context, int type, boolean expandable, boolean requery, Limiter limiter)
public MediaAdapter(SongSelector activity, int type, boolean expandable, Limiter limiter)
{
super(context, null, requery);
super(activity, null, false);
mContext = context;
mActivity = activity;
mType = type;
mExpandable = expandable;
mLimiter = limiter;
@ -151,11 +149,8 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
throw new IllegalArgumentException("Invalid value for type: " + type);
}
setFilterQueryProvider(this);
requery();
if (mPaint == null) {
Resources res = context.getResources();
Resources res = activity.getResources();
mExpander = BitmapFactory.decodeResource(res, R.drawable.expander_arrow);
mTextSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, res.getDisplayMetrics());
@ -165,36 +160,17 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
}
}
/**
* Update the data in the adapter.
*/
public final void requery()
{
changeCursor(runQuery(mConstraint));
}
/**
* Perform filtering on a background thread.
*
* @param constraint The terms to filter on, separated by spaces. Only
* media that contain all of the terms (in any order) will be displayed
* after filtering is complete.
* @param listener A listener to be called when filtering is complete or
* null.
*/
public void filter(CharSequence constraint, Filter.FilterListener listener)
public void filter(String constraint)
{
mConstraint = constraint;
super.getFilter().filter(constraint, listener);
}
/**
* Override getFilter to prevent access.
*/
@Override
public Filter getFilter()
{
throw new UnsupportedOperationException("Do not use getFilter directly. Call filter instead.");
mActivity.runQuery(this);
}
/**
@ -222,13 +198,12 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
}
/**
* Query the content provider using the given constraint as a filter.
*
* @return The Cursor returned by the query.
* Query the backing content provider. Should be called on a background
* thread.
*/
public Cursor runQuery(CharSequence constraint)
public void runQuery()
{
ContentResolver resolver = mContext.getContentResolver();
ContentResolver resolver = mActivity.getContentResolver();
String[] projection;
if (mFields.length == 1)
@ -243,6 +218,7 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
if (defaultSelection != null)
selection.append(defaultSelection);
String constraint = mConstraint;
if (constraint != null && constraint.length() != 0) {
String[] needles;
@ -250,11 +226,11 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
// into a list of collation keys. Otherwise, just split the
// constraint with no modification.
if (mFieldKeys != null) {
String colKey = MediaStore.Audio.keyFor(constraint.toString());
String colKey = MediaStore.Audio.keyFor(constraint);
String spaceColKey = DatabaseUtils.getCollationKey(" ");
needles = colKey.split(spaceColKey);
} else {
needles = constraint.toString().split("\\s+");
needles = constraint.split("\\s+");
}
int size = needles.length;
@ -278,19 +254,23 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
}
}
if (mLimiter != null) {
if (mLimiter.type == MediaUtils.TYPE_GENRE) {
// Genre is not standard metadata for MediaStore.Audio.Media.
// We have to query it through a separate provider. : /
return MediaUtils.queryGenre(mContext, mLimiter.id, projection, selection.toString(), selectionArgs);
} else {
Cursor cursor;
if (mLimiter != null && mLimiter.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);
} else {
if (mLimiter != null) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(mLimiter.selection);
}
cursor = resolver.query(mStore, projection, selection.toString(), selectionArgs, getSortOrder());
}
return resolver.query(mStore, projection, selection.toString(), selectionArgs, getSortOrder());
mActivity.changeCursor(this, cursor);
}
/**
@ -299,15 +279,11 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
* media item.
*
* @param limiter The limiter, created by MediaView.getLimiter()
* @param async If true, update the adapter in the background.
*/
public final void setLimiter(Limiter limiter, boolean async)
public final void setLimiter(Limiter limiter)
{
mLimiter = limiter;
if (async)
super.getFilter().filter(mConstraint);
else
requery();
mActivity.runQuery(this);
}
/**
@ -549,7 +525,7 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
* Builds a limiter based off of the media represented by this view.
*
* @see MediaAdapter#getLimiter()
* @see MediaAdapter#setLimiter(String[], boolean)
* @see MediaAdapter#setLimiter(MediaAdapter.Limiter)
*/
public final Limiter getLimiter()
{

View File

@ -22,7 +22,6 @@
package org.kreed.vanilla;
import android.content.Context;
import android.provider.MediaStore;
/**
@ -34,15 +33,14 @@ public class SongMediaAdapter extends MediaAdapter {
/**
* Construct a MediaAdapter backed by MediaStore.Audio.Media.
*
* @param context A Context to use
* @param activity The activity that owns this adapter.
* @param expandable Whether an expander arrow should by shown to the right
* of views
* @param requery If true, automatically update the adapter when the
* provider changes
* @param limiter An initial limiter to use.
*/
public SongMediaAdapter(Context context, boolean expandable, boolean requery, MediaAdapter.Limiter limiter)
public SongMediaAdapter(SongSelector activity, boolean expandable, MediaAdapter.Limiter limiter)
{
super(context, MediaUtils.TYPE_SONG, expandable, requery, limiter);
super(activity, MediaUtils.TYPE_SONG, expandable, limiter);
}
@Override

View File

@ -26,6 +26,7 @@ import android.content.ContentResolver;
import android.content.Intent;
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
@ -49,7 +50,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@ -57,7 +57,10 @@ import android.widget.TabHost;
import android.widget.TextView;
import android.widget.Toast;
public class SongSelector extends PlaybackActivity implements AdapterView.OnItemClickListener, TextWatcher, TabHost.OnTabChangeListener, Filter.FilterListener {
/**
* The library activity.
*/
public class SongSelector extends PlaybackActivity implements AdapterView.OnItemClickListener, TextWatcher, TabHost.OnTabChangeListener {
/**
* The number of tabs in the song selector.
*/
@ -93,6 +96,14 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private MediaAdapter mGenreAdapter;
private MediaAdapter mCurrentAdapter;
private ContentObserver mPlaylistObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange)
{
runQuery(mPlaylistAdapter);
}
};
@Override
public void onCreate(Bundle state)
{
@ -121,13 +132,15 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mLimiterViews = (ViewGroup)findViewById(R.id.limiter_layout);
mArtistAdapter = setupView(R.id.artist_list, new MediaAdapter(this, MediaUtils.TYPE_ARTIST, true, false, null));
mAlbumAdapter = setupView(R.id.album_list, new MediaAdapter(this, MediaUtils.TYPE_ALBUM, true, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_albums")));
mSongAdapter = setupView(R.id.song_list, new SongMediaAdapter(this, false, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_songs")));
mPlaylistAdapter = setupView(R.id.playlist_list, new MediaAdapter(this, MediaUtils.TYPE_PLAYLIST, false, true, null));
mGenreAdapter = setupView(R.id.genre_list, new MediaAdapter(this, MediaUtils.TYPE_GENRE, true, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_genres")));
mArtistAdapter = setupView(R.id.artist_list, new MediaAdapter(this, MediaUtils.TYPE_ARTIST, true, null));
mAlbumAdapter = setupView(R.id.album_list, new MediaAdapter(this, MediaUtils.TYPE_ALBUM, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_albums")));
mSongAdapter = setupView(R.id.song_list, new SongMediaAdapter(this, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_songs")));
mPlaylistAdapter = setupView(R.id.playlist_list, new MediaAdapter(this, MediaUtils.TYPE_PLAYLIST, false, null));
mGenreAdapter = setupView(R.id.genre_list, new MediaAdapter(this, MediaUtils.TYPE_GENRE, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_genres")));
mAdapters = new MediaAdapter[] { mArtistAdapter, mAlbumAdapter, mSongAdapter, mPlaylistAdapter, mGenreAdapter };
getContentResolver().registerContentObserver(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mPlaylistObserver);
mCurrentAdapter = mAdapters[mTabHost.getCurrentTab()];
mTabHost.setOnTabChangedListener(this);
@ -140,6 +153,9 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mTabHost.setCurrentTab(currentTab);
mTextFilter.setText(state.getString("filter"));
}
// query adapters
onMediaChange();
}
@Override
@ -290,22 +306,27 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private int setLimiter(MediaAdapter.Limiter limiter)
{
if (limiter == null) {
mAlbumAdapter.setLimiter(null, true);
mSongAdapter.setLimiter(null, true);
mAlbumAdapter.setLimiter(null);
mSongAdapter.setLimiter(null);
return -1;
}
switch (limiter.type) {
case MediaUtils.TYPE_ALBUM:
mSongAdapter.setLimiter(limiter, false);
// Clear the cursor so we don't have the old selection showing when
// we switch to that tab.
mSongAdapter.changeCursor(null);
mSongAdapter.setLimiter(limiter);
return 2;
case MediaUtils.TYPE_ARTIST:
mAlbumAdapter.setLimiter(limiter, false);
mSongAdapter.setLimiter(limiter, true);
mAlbumAdapter.changeCursor(null);
mAlbumAdapter.setLimiter(limiter);
mSongAdapter.setLimiter(limiter);
return 1;
case MediaUtils.TYPE_GENRE:
mAlbumAdapter.setLimiter(null, true);
mSongAdapter.setLimiter(limiter, false);
mSongAdapter.changeCursor(null);
mSongAdapter.setLimiter(limiter);
mAlbumAdapter.setLimiter(null);
return 2;
default:
throw new IllegalArgumentException("Unsupported limiter type: " + limiter.type);
@ -334,8 +355,15 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
public void onTextChanged(CharSequence text, int start, int before, int count)
{
MediaAdapter adapter = mCurrentAdapter;
if (adapter != null)
adapter.filter(text, this);
if (adapter != null) {
String filter = text.toString();
adapter.filter(filter);
for (int i = TAB_COUNT; --i != -1; ) {
if (mAdapters[i] != adapter)
mAdapters[i].filter(filter);
}
}
}
private void updateLimiterViews()
@ -585,13 +613,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
}
}
public void onFilterComplete(int count)
{
CharSequence text = mTextFilter.getText();
for (int i = TAB_COUNT; --i != -1; )
mAdapters[i].filter(text, null);
}
/**
* Hook up a ListView to this Activity and the supplied adapter
*
@ -629,6 +650,11 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
* that the name will be taken from.
*/
private static final int MSG_RENAME_PLAYLIST = 13;
/**
* Called by MediaAdapters to requery their data on the worker thread.
* obj will contain the MediaAdapter.
*/
public static final int MSG_RUN_QUERY = 14;
@Override
public boolean handleMessage(Message message)
@ -650,7 +676,11 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
NewPlaylistDialog dialog = (NewPlaylistDialog)message.obj;
if (dialog.isAccepted())
Playlist.renamePlaylist(this, message.arg2, dialog.getText());
break;
}
case MSG_RUN_QUERY:
((MediaAdapter)message.obj).runQuery();
break;
default:
return super.handleMessage(message);
}
@ -658,16 +688,39 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
return true;
}
/**
* Schedule a query to be run for the given adapter on the worker thread.
*
* @param adapter The adapter to run the query for.
*/
public void runQuery(MediaAdapter adapter)
{
mHandler.removeMessages(MSG_RUN_QUERY, adapter);
mHandler.sendMessage(mHandler.obtainMessage(MSG_RUN_QUERY, adapter));
}
/**
* Update the given adapter with the given cursor on the UI thread.
*
* @param adapter The adapter to update.
* @param cursor The cursor to update with.
*/
public void changeCursor(final MediaAdapter adapter, final Cursor cursor)
{
runOnUiThread(new Runnable() {
@Override
public void run()
{
adapter.changeCursor(cursor);
}
});
}
@Override
public void onMediaChange()
{
runOnUiThread(new Runnable() {
public void run()
{
for (int i = 0; i != TAB_COUNT; ++i)
mAdapters[i].requery();
}
});
for (int i = 0; i != TAB_COUNT; ++i)
runQuery(mAdapters[i]);
}
private void setSearchBoxVisible(boolean visible)