Refector library limiters
This commit is contained in:
parent
444b7f588b
commit
d1d875fd5f
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user