Run library queries on worker thread
This commit is contained in:
parent
e23598aef1
commit
af069c208b
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user