diff --git a/src/org/kreed/vanilla/MediaAdapter.java b/src/org/kreed/vanilla/MediaAdapter.java index 5e458d44..394a57fa 100644 --- a/src/org/kreed/vanilla/MediaAdapter.java +++ b/src/org/kreed/vanilla/MediaAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Christopher Eby + * Copyright (C) 2010, 2011 Christopher Eby * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -45,6 +45,7 @@ import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.Filter; import android.widget.FilterQueryProvider; +import java.io.Serializable; /** * MediaAdapter provides an adapter backed by a MediaStore content provider. @@ -87,14 +88,8 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { * A limiter is used for filtering. The intention is to restrict items * displayed in the list to only those of a specific artist or album, as * selected through an expander arrow in a broader MediaAdapter list. - * - * Each element in the limiter corresponds to a field in mFields. If - * mLimiter is non-null, only songs containing the field matching the - * last element of mLimiter will be displayed. Elements before the last - * element are not used; they are present to make it more convenient to - * display a UI representation of the limiter. */ - private String[] mLimiter; + private Limiter mLimiter; /** * The last constraint used in a call to filter. */ @@ -112,13 +107,15 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { * 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) + public MediaAdapter(Context context, int type, boolean expandable, boolean requery, Limiter limiter) { super(context, null, requery); mType = type; mExpandable = expandable; + mLimiter = limiter; switch (type) { case MediaUtils.TYPE_ARTIST: @@ -227,21 +224,15 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { StringBuilder selection = new StringBuilder(); String[] selectionArgs; - String limiter; String defaultSelection = getDefaultSelection(); if (defaultSelection != null) selection.append(defaultSelection); if (mLimiter != null) { - int i = Math.min(mLimiter.length, mFields.length) - 1; if (selection.length() != 0) selection.append(" AND "); - selection.append(mFields[i]); - selection.append(" = ?"); - limiter = mLimiter[i]; - } else { - limiter = null; + selection.append(mLimiter.selection); } if (constraint != null && constraint.length() != 0) { @@ -259,14 +250,8 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { } int size = needles.length; - if (limiter != null) - ++size; selectionArgs = new String[size]; int i = 0; - if (limiter != null) { - selectionArgs[0] = limiter; - i = 1; - } String[] keySource = mFieldKeys == null ? mFields : mFieldKeys; String keys = keySource[0]; @@ -276,16 +261,14 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { for (int j = 0; j != needles.length; ++i, ++j) { selectionArgs[i] = '%' + needles[j] + '%'; - // If we have something in the selection (i.e. i > 0), we must - // have something in the selection, so we can skip the more + // If we have something in the selection args (i.e. i > 0), we + // must have something in the selection, so we can skip the more // costly direct check of the selection length. if (i != 0 || selection.length() != 0) selection.append(" AND "); selection.append(keys); selection.append(" LIKE ?"); } - } else if (limiter != null) { - selectionArgs = new String[] { limiter }; } else { selectionArgs = null; } @@ -304,13 +287,10 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { * displayed media to only those that are children of a given parent * media item. * - * @param limiter An array with each element corresponding to a field in - * this adapter. The last field in the array will be used as the limiter; - * only media that are children of the media with the title of the last - * element will be displayed. + * @param limiter The limiter, created by MediaView.getLimiter() * @param async If true, update the adapter in the background. */ - public final void setLimiter(String[] limiter, boolean async) + public final void setLimiter(Limiter limiter, boolean async) { mLimiter = limiter; if (async) @@ -321,26 +301,13 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { /** * Returns the limiter currently active on this adapter or null if none are - * active. The limiter is a list of titles, each corresponding to a field - * in the fields in this adapter. The last field is the most specific. Only - * media that are children of the media with the last element's title are - * displayed. + * active. */ - public final String[] getLimiter() + public final Limiter getLimiter() { return mLimiter; } - /** - * Returns the length of the limiter or 0 if no limiter is active. - */ - public final int getLimiterLength() - { - if (mLimiter == null) - return 0; - return mLimiter.length; - } - /** * Update the values in the given view. */ @@ -573,22 +540,23 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { * @see MediaAdapter#getLimiter() * @see MediaAdapter#setLimiter(String[], boolean) */ - public final String[] getLimiter() + public final Limiter getLimiter() { - ContentResolver resolver = getContext().getContentResolver(); - String selection = mFields[mFields.length - 1] + " = ?"; - String[] selectionArgs = { mTitle }; - String[] projection = new String[mFields.length + 1]; - projection[0] = BaseColumns._ID; - System.arraycopy(mFields, 0, projection, 1, mFields.length); - - Cursor cursor = resolver.query(mStore, projection, selection, selectionArgs, null); - cursor.moveToNext(); - String[] result = new String[cursor.getColumnCount() - 1]; - for (int i = result.length; --i != -1; ) - result[i] = cursor.getString(i + 1); - - return result; + String[] fields; + String field; + switch (mType) { + case MediaUtils.TYPE_ARTIST: + fields = new String[] { mTitle }; + field = MediaStore.Audio.Media.ARTIST_ID; + break; + case MediaUtils.TYPE_ALBUM: + fields = new String[] { mSubTitle, mTitle }; + field = MediaStore.Audio.Media.ALBUM_ID; + break; + default: + throw new IllegalStateException("getLimiter() is not supported for media type: " + mType); + } + return new Limiter(mId, mType, field, fields); } /** @@ -602,4 +570,20 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider { return false; } } + + /** + * Limiter is a constraint for MediaAdapters used when a row is "expanded". + */ + public static class Limiter implements Serializable { + public final String[] names; + public final int type; + public final String selection; + + public Limiter(long id, int type, String field, String[] names) + { + this.type = type; + this.names = names; + selection = String.format("%s=%d", field, id); + } + } } diff --git a/src/org/kreed/vanilla/SongMediaAdapter.java b/src/org/kreed/vanilla/SongMediaAdapter.java index 43506d1e..371686d8 100644 --- a/src/org/kreed/vanilla/SongMediaAdapter.java +++ b/src/org/kreed/vanilla/SongMediaAdapter.java @@ -40,9 +40,9 @@ public class SongMediaAdapter extends MediaAdapter { * @param requery If true, automatically update the adapter when the * provider changes */ - public SongMediaAdapter(Context context, boolean expandable, boolean requery) + public SongMediaAdapter(Context context, boolean expandable, boolean requery, MediaAdapter.Limiter limiter) { - super(context, MediaUtils.TYPE_SONG, expandable, requery); + super(context, MediaUtils.TYPE_SONG, expandable, requery, limiter); } @Override @@ -54,7 +54,8 @@ public class SongMediaAdapter extends MediaAdapter { @Override protected String getSortOrder() { - if (getLimiter() != null && getLimiter().length == 2) + Limiter limiter = getLimiter(); + if (limiter != null && limiter.type == MediaUtils.TYPE_ALBUM) return MediaStore.Audio.Media.TRACK; return super.getSortOrder(); } diff --git a/src/org/kreed/vanilla/SongSelector.java b/src/org/kreed/vanilla/SongSelector.java index 2404eba3..17cf4e87 100644 --- a/src/org/kreed/vanilla/SongSelector.java +++ b/src/org/kreed/vanilla/SongSelector.java @@ -22,9 +22,11 @@ package org.kreed.vanilla; +import android.content.ContentResolver; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Resources; +import android.content.SharedPreferences; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.PaintDrawable; @@ -32,6 +34,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.preference.PreferenceManager; +import android.provider.MediaStore; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -59,10 +62,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem * The number of tabs in the song selector. */ private static final int TAB_COUNT = 4; - /** - * The number of tabs in the song selector that should be limited. - */ - private static final int LIMIT_COUNT = 3; private TabHost mTabHost; @@ -82,11 +81,12 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem private long mLastActedId; - MediaAdapter getAdapter(int tab) - { - ListView list = (ListView)mTabHost.getTabContentView().getChildAt(tab); - return (MediaAdapter)list.getAdapter(); - } + private MediaAdapter[] mAdapters; + private MediaAdapter mArtistAdapter; + private MediaAdapter mAlbumAdapter; + private MediaAdapter mSongAdapter; + private MediaAdapter mPlaylistAdapter; + private MediaAdapter mCurrentAdapter; @Override public void onCreate(Bundle state) @@ -97,7 +97,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setup(); - mTabHost.setOnTabChangedListener(this); Resources res = getResources(); mTabHost.addTab(mTabHost.newTabSpec("tab_artists").setIndicator(res.getText(R.string.artists), res.getDrawable(R.drawable.tab_artists)).setContent(R.id.artist_list)); @@ -115,10 +114,13 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem mLimiterViews = (ViewGroup)findViewById(R.id.limiter_layout); - setupView(R.id.artist_list, new MediaAdapter(this, MediaUtils.TYPE_ARTIST, true, false)); - setupView(R.id.album_list, new MediaAdapter(this, MediaUtils.TYPE_ALBUM, true, false)); - setupView(R.id.song_list, new SongMediaAdapter(this, false, false)); - setupView(R.id.playlist_list, new MediaAdapter(this, MediaUtils.TYPE_PLAYLIST, false, true)); + 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)); + mAdapters = new MediaAdapter[] { mArtistAdapter, mAlbumAdapter, mSongAdapter, mPlaylistAdapter }; + + mTabHost.setOnTabChangedListener(this); if (state != null) { if (state.getBoolean("search_box_visible")) @@ -127,9 +129,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem if (currentTab != -1) mTabHost.setCurrentTab(currentTab); mTextFilter.setText(state.getString("filter")); - for (int i = 0; i != LIMIT_COUNT; ++i) - getAdapter(i).setLimiter(state.getStringArray("limiter_" + i), true); - updateLimiterViews(); } } @@ -184,8 +183,8 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem out.putBoolean("search_box_visible", mSearchBoxVisible); out.putInt("current_tab", mTabHost.getCurrentTab()); out.putString("filter", mTextFilter.getText().toString()); - for (int i = 0; i != LIMIT_COUNT; ++i) - out.putStringArray("limiter_" + i, getAdapter(i).getLimiter()); + out.putSerializable("limiter_albums", mAlbumAdapter.getLimiter()); + out.putSerializable("limiter_songs", mSongAdapter.getLimiter()); } @Override @@ -266,13 +265,33 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem private void expand(MediaAdapter.MediaView view) { - String[] limiter = view.getLimiter(); + mTabHost.setCurrentTab(setLimiter(view.getLimiter())); + } - getAdapter(limiter.length).setLimiter(limiter, false); - mTabHost.setCurrentTab(limiter.length); + /** + * Update the adapters with the given limiter. + * + * @return The tab to "expand" to + */ + private int setLimiter(MediaAdapter.Limiter limiter) + { + if (limiter == null) { + mAlbumAdapter.setLimiter(null, true); + mSongAdapter.setLimiter(null, true); + return -1; + } - for (int i = limiter.length + 1; i < LIMIT_COUNT; ++i) - getAdapter(i).setLimiter(limiter, true); + switch (limiter.type) { + case MediaUtils.TYPE_ALBUM: + mSongAdapter.setLimiter(limiter, false); + return 2; + case MediaUtils.TYPE_ARTIST: + mAlbumAdapter.setLimiter(limiter, false); + mSongAdapter.setLimiter(limiter, true); + return 1; + default: + throw new IllegalArgumentException("Unsupported limiter type: " + limiter.type); + } } public void onItemClick(AdapterView list, View view, int pos, long id) @@ -296,7 +315,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem public void onTextChanged(CharSequence text, int start, int before, int count) { - MediaAdapter adapter = getAdapter(mTabHost.getCurrentTab()); + MediaAdapter adapter = mCurrentAdapter; if (adapter != null) adapter.filter(text, this); } @@ -308,13 +327,14 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem mLimiterViews.removeAllViews(); - MediaAdapter adapter = getAdapter(mTabHost.getCurrentTab()); + MediaAdapter adapter = mCurrentAdapter; if (adapter == null) return; - String[] limiter = adapter.getLimiter(); - if (limiter == null) + MediaAdapter.Limiter limiterData = adapter.getLimiter(); + if (limiterData == null) return; + String[] limiter = limiterData.names; LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); params.leftMargin = 5; @@ -338,6 +358,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem public void onTabChanged(String tabId) { + mCurrentAdapter = mAdapters[mTabHost.getCurrentTab()]; updateLimiterViews(); } @@ -352,22 +373,31 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem } else if (view == mCover) { startActivity(new Intent(this, FullPlaybackActivity.class)); } else if (view.getTag() != null) { + // a limiter view was clicked + int i = (Integer)view.getTag(); - String[] limiter; - if (i == 0) { - limiter = null; - } else { - String[] oldLimiter = getAdapter(mTabHost.getCurrentTab()).getLimiter(); - limiter = new String[i]; - System.arraycopy(oldLimiter, 0, limiter, 0, i); - } - - for (int j = LIMIT_COUNT; --j != -1; ) { - MediaAdapter adapter = getAdapter(j); - if (adapter.getLimiterLength() > i) - adapter.setLimiter(limiter, true); + if (i == 1) { + // generate the artist limiter (we need to query the artist id) + MediaAdapter.Limiter limiter = mSongAdapter.getLimiter(); + assert(limiter.type == MediaUtils.TYPE_ALBUM); + + ContentResolver resolver = getContentResolver(); + Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + String[] projection = new String[] { MediaStore.Audio.Media.ARTIST_ID }; + Cursor cursor = resolver.query(uri, projection, limiter.selection, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + limiter = new MediaAdapter.Limiter(cursor.getLong(0), MediaUtils.TYPE_ARTIST, MediaStore.Audio.Media.ARTIST_ID, new String[] { limiter.names[0] }); + setLimiter(limiter); + updateLimiterViews(); + cursor.close(); + return; + } + cursor.close(); + } } + setLimiter(null); updateLimiterViews(); } else { super.onClick(view); @@ -545,7 +575,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem { CharSequence text = mTextFilter.getText(); for (int i = TAB_COUNT; --i != -1; ) - getAdapter(i).filter(text, null); + mAdapters[i].filter(text, null); } /** @@ -554,12 +584,13 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem * @param id The id of the ListView * @param adapter The adapter to be used */ - private void setupView(int id, final MediaAdapter adapter) + private MediaAdapter setupView(int id, MediaAdapter adapter) { - final ListView view = (ListView)findViewById(id); + ListView view = (ListView)findViewById(id); view.setOnItemClickListener(this); view.setOnCreateContextMenuListener(this); view.setAdapter(adapter); + return adapter; } /** @@ -620,7 +651,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem public void run() { for (int i = 0; i != TAB_COUNT; ++i) - getAdapter(i).requery(); + mAdapters[i].requery(); } }); }