diff --git a/src/org/kreed/vanilla/MediaAdapter.java b/src/org/kreed/vanilla/MediaAdapter.java index 67f42314..29b6a611 100644 --- a/src/org/kreed/vanilla/MediaAdapter.java +++ b/src/org/kreed/vanilla/MediaAdapter.java @@ -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 type 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() { diff --git a/src/org/kreed/vanilla/SongMediaAdapter.java b/src/org/kreed/vanilla/SongMediaAdapter.java index 371686d8..faa8e333 100644 --- a/src/org/kreed/vanilla/SongMediaAdapter.java +++ b/src/org/kreed/vanilla/SongMediaAdapter.java @@ -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 diff --git a/src/org/kreed/vanilla/SongSelector.java b/src/org/kreed/vanilla/SongSelector.java index 09e0ad16..5df84b5c 100644 --- a/src/org/kreed/vanilla/SongSelector.java +++ b/src/org/kreed/vanilla/SongSelector.java @@ -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)