Refector library limiters

This commit is contained in:
Christopher Eby 2011-08-28 00:08:57 -05:00
parent 444b7f588b
commit d1d875fd5f
3 changed files with 126 additions and 110 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 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
@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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();
}
});
}