Rework MediaAdapter to be backed by a Cursor instead of an array
This reduces memory requirements and amount of code
This commit is contained in:
parent
0571a3189c
commit
e21e1c6532
@ -18,50 +18,121 @@
|
||||
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.FilterQueryProvider;
|
||||
|
||||
public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
private Context mContext;
|
||||
private OnClickListener mExpanderListener;
|
||||
public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
|
||||
public static final String[] ARTIST_FIELDS = { MediaStore.Audio.Artists.ARTIST };
|
||||
public static final String[] ARTIST_FIELD_KEYS = { MediaStore.Audio.Artists.ARTIST_KEY };
|
||||
public static final String[] ALBUM_FIELDS = { MediaStore.Audio.Albums.ARTIST, MediaStore.Audio.Albums.ALBUM };
|
||||
// Why is there no artist_key column constant in the album MediaStore? The column does seem to exist.
|
||||
public static final String[] ALBUM_FIELD_KEYS = { "artist_key", MediaStore.Audio.Albums.ALBUM_KEY };
|
||||
public static final String[] SONG_FIELDS = { MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE };
|
||||
public static final String[] SONG_FIELD_KEYS = { MediaStore.Audio.Media.ARTIST_KEY, MediaStore.Audio.Media.ALBUM_KEY, MediaStore.Audio.Media.TITLE_KEY };
|
||||
|
||||
private List<SongData> mObjects;
|
||||
private SongData[] mAllObjects;
|
||||
private ArrayFilter mFilter;
|
||||
private long mLimiter;
|
||||
private SongData mLimiterData;
|
||||
private CharSequence mPublishedFilter;
|
||||
private long mPublishedLimiter;
|
||||
private Uri mStore;
|
||||
private String[] mFields;
|
||||
private String[] mFieldKeys;
|
||||
private View.OnClickListener mExpanderListener;
|
||||
private String mSelection;
|
||||
private String[] mLimiter;
|
||||
private CharSequence mConstraint;
|
||||
|
||||
private int mPrimaryField;
|
||||
private int mSecondaryField;
|
||||
|
||||
public MediaAdapter(Context context, SongData[] allObjects, int primaryField, int secondaryField, View.OnClickListener expanderListener)
|
||||
public MediaAdapter(Context context, Uri store, String[] fields, String[] fieldKeys, View.OnClickListener expanderListener, String selection)
|
||||
{
|
||||
mContext = context;
|
||||
mAllObjects = allObjects;
|
||||
mPrimaryField = primaryField;
|
||||
mSecondaryField = secondaryField;
|
||||
super(context, null, true);
|
||||
|
||||
mStore = store;
|
||||
mFields = fields;
|
||||
mFieldKeys = fieldKeys;
|
||||
mExpanderListener = expanderListener;
|
||||
mSelection = selection;
|
||||
|
||||
setFilterQueryProvider(this);
|
||||
|
||||
changeCursor(runQuery(null));
|
||||
}
|
||||
|
||||
public void filter(CharSequence constraint, Filter.FilterListener listener)
|
||||
{
|
||||
mConstraint = constraint;
|
||||
getFilter().filter(constraint, listener);
|
||||
}
|
||||
|
||||
public Cursor runQuery(CharSequence constraint)
|
||||
{
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
|
||||
StringBuilder selection = new StringBuilder();
|
||||
String[] selectionArgs;
|
||||
String limiter;
|
||||
|
||||
if (mSelection != null)
|
||||
selection.append(mSelection);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (constraint != null && constraint.length() != 0) {
|
||||
String[] constraints = constraint.toString().split("\\s+");
|
||||
int size = constraints.length;
|
||||
if (limiter != null)
|
||||
++size;
|
||||
selectionArgs = new String[size];
|
||||
int i = 0;
|
||||
if (limiter != null) {
|
||||
selectionArgs[0] = limiter;
|
||||
i = 1;
|
||||
}
|
||||
String keys = mFieldKeys[0];
|
||||
for (int j = 1; j != mFieldKeys.length; ++j)
|
||||
keys += "||" + mFieldKeys[j];
|
||||
for (int j = 0; j != constraints.length; ++i, ++j) {
|
||||
selectionArgs[i] = '%' + MediaStore.Audio.keyFor(constraints[j]) + '%';
|
||||
|
||||
if (j != 0 || selection.length() != 0)
|
||||
selection.append(" AND ");
|
||||
selection.append(keys);
|
||||
selection.append(" LIKE ?");
|
||||
}
|
||||
} else {
|
||||
if (limiter != null)
|
||||
selectionArgs = new String[] { limiter };
|
||||
else
|
||||
selectionArgs = null;
|
||||
}
|
||||
|
||||
String[] projection;
|
||||
if (mFields.length == 1)
|
||||
projection = new String[] { BaseColumns._ID, mFields[0] };
|
||||
else
|
||||
projection = new String[] { BaseColumns._ID, mFields[mFields.length - 1], mFields[0] };
|
||||
|
||||
return resolver.query(mStore, projection, selection.toString(), selectionArgs, mFieldKeys[mFieldKeys.length - 1]);
|
||||
}
|
||||
|
||||
public final boolean hasExpanders()
|
||||
@ -69,231 +140,34 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
return mExpanderListener != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
MediaView view = null;
|
||||
try {
|
||||
view = (MediaView)convertView;
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
|
||||
if (view == null)
|
||||
view = new MediaView(mContext);
|
||||
|
||||
view.updateMedia(get(position));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public Filter getFilter()
|
||||
{
|
||||
if (mFilter == null)
|
||||
mFilter = new ArrayFilter();
|
||||
return mFilter;
|
||||
}
|
||||
|
||||
private static final String[] mRanges = { ".", "[2abc]", "[3def]", "[4ghi]", "[5jkl]", "[6mno]", "[7pqrs]", "[8tuv]", "[9wxyz]"};
|
||||
private static Matcher createMatcher(String input)
|
||||
{
|
||||
String patternString = "";
|
||||
for (int i = 0, end = input.length(); i != end; ++i) {
|
||||
char c = input.charAt(i);
|
||||
int value = c - '1';
|
||||
if (value >= 0 && value < 9)
|
||||
patternString += mRanges[value];
|
||||
else
|
||||
patternString += c;
|
||||
}
|
||||
|
||||
return Pattern.compile(patternString, Pattern.CASE_INSENSITIVE).matcher("");
|
||||
}
|
||||
|
||||
private class ArrayFilter extends Filter {
|
||||
protected class ArrayFilterResults extends FilterResults {
|
||||
public long limiter;
|
||||
|
||||
public ArrayFilterResults(List<SongData> list, long limiter)
|
||||
{
|
||||
values = list;
|
||||
count = list.size();
|
||||
this.limiter = limiter;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence filter)
|
||||
{
|
||||
List<SongData> list;
|
||||
|
||||
if (filter != null && filter.length() == 0)
|
||||
filter = null;
|
||||
|
||||
if ((filter == null && mPublishedFilter == null || mPublishedFilter != null && mPublishedFilter.equals(filter)) && mLimiter == mPublishedLimiter) {
|
||||
list = mObjects;
|
||||
} else if (filter == null && mLimiter == -1) {
|
||||
list = Arrays.asList(mAllObjects);
|
||||
} else {
|
||||
Matcher[] matchers = null;
|
||||
if (filter != null) {
|
||||
String[] words = filter.toString().split("\\s+");
|
||||
matchers = new Matcher[words.length];
|
||||
for (int i = words.length; --i != -1; )
|
||||
matchers[i] = createMatcher(words[i]);
|
||||
}
|
||||
|
||||
int limiterField = limiterField(mLimiter);
|
||||
long limiterId = limiterId(mLimiter);
|
||||
|
||||
int count = mAllObjects.length;
|
||||
ArrayList<SongData> newValues = new ArrayList<SongData>();
|
||||
newValues.ensureCapacity(count);
|
||||
|
||||
outer:
|
||||
for (int i = 0; i != count; ++i) {
|
||||
SongData song = mAllObjects[i];
|
||||
|
||||
if (mLimiter != -1 && song.getFieldId(limiterField) != limiterId)
|
||||
continue;
|
||||
|
||||
if (filter != null) {
|
||||
for (int j = matchers.length; --j != -1; ) {
|
||||
if (matchers[j].reset(song.artist).find())
|
||||
continue;
|
||||
if (mPrimaryField > 1 && matchers[j].reset(song.album).find())
|
||||
continue;
|
||||
if (mPrimaryField > 2 && matchers[j].reset(song.title).find())
|
||||
continue;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
newValues.add(song);
|
||||
}
|
||||
|
||||
newValues.trimToSize();
|
||||
|
||||
list = newValues;
|
||||
}
|
||||
|
||||
return new ArrayFilterResults(list, mLimiter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void publishResults(CharSequence filter, FilterResults results)
|
||||
{
|
||||
mObjects = (List<SongData>)results.values;
|
||||
mPublishedFilter = filter == null || filter.length() == 0 ? null : filter;
|
||||
mPublishedLimiter = ((ArrayFilterResults)results).limiter;
|
||||
|
||||
if (results.count == 0)
|
||||
notifyDataSetInvalidated();
|
||||
else
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideAll()
|
||||
{
|
||||
mObjects = new ArrayList<SongData>();
|
||||
notifyDataSetInvalidated();
|
||||
}
|
||||
|
||||
public void setLimiter(long limiter, SongData data)
|
||||
public final void setLimiter(String[] limiter)
|
||||
{
|
||||
mLimiter = limiter;
|
||||
mLimiterData = data;
|
||||
getFilter().filter(mPublishedFilter);
|
||||
getFilter().filter(mConstraint);
|
||||
}
|
||||
|
||||
public final long getLimiter()
|
||||
public final String[] getLimiter()
|
||||
{
|
||||
return mLimiter;
|
||||
}
|
||||
|
||||
public final int getLimiterField()
|
||||
public final int getLimiterLength()
|
||||
{
|
||||
if (mLimiter == -1)
|
||||
return -1;
|
||||
return limiterField(mLimiter);
|
||||
}
|
||||
|
||||
public final SongData getLimiterData()
|
||||
{
|
||||
return mLimiterData;
|
||||
}
|
||||
|
||||
private static final int ID_SHIFT = 2;
|
||||
private static final int FIELD_MASK = ~(~0 << ID_SHIFT);
|
||||
|
||||
public static long makeLimiter(int field, SongData data)
|
||||
{
|
||||
return (data.getFieldId(field) << ID_SHIFT) + (field & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int limiterField(long limiter)
|
||||
{
|
||||
return (int)(limiter & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static long limiterId(long limiter)
|
||||
{
|
||||
return limiter >> ID_SHIFT;
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
if (mObjects == null) {
|
||||
if (mAllObjects == null)
|
||||
return 0;
|
||||
return mAllObjects.length;
|
||||
}
|
||||
return mObjects.size();
|
||||
}
|
||||
|
||||
public SongData get(int i)
|
||||
{
|
||||
if (mObjects == null) {
|
||||
if (mAllObjects == null)
|
||||
return null;
|
||||
return mAllObjects[i];
|
||||
}
|
||||
if (i >= mObjects.size())
|
||||
return null;
|
||||
return mObjects.get(i);
|
||||
}
|
||||
|
||||
public Object getItem(int i)
|
||||
{
|
||||
return get(i);
|
||||
}
|
||||
|
||||
public long getItemId(int i)
|
||||
{
|
||||
SongData song = get(i);
|
||||
if (song == null)
|
||||
if (mLimiter == null)
|
||||
return 0;
|
||||
return song.getFieldId(mPrimaryField);
|
||||
return mLimiter.length;
|
||||
}
|
||||
|
||||
public Intent buildSongIntent(int action, int pos)
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
SongData song = get(pos);
|
||||
if (song == null)
|
||||
return null;
|
||||
((MediaView)view).updateMedia(cursor);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(mContext, PlaybackService.class);
|
||||
intent.putExtra("type", mPrimaryField);
|
||||
intent.putExtra("action", action);
|
||||
intent.putExtra("id", song.getFieldId(mPrimaryField));
|
||||
intent.putExtra("title", song.getField(mPrimaryField));
|
||||
return intent;
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent)
|
||||
{
|
||||
return new MediaView(context);
|
||||
}
|
||||
|
||||
private static float mTextSize = -1;
|
||||
@ -302,7 +176,9 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
private int mViewHeight = -1;
|
||||
|
||||
public class MediaView extends View {
|
||||
private SongData mData;
|
||||
private long mId;
|
||||
private String mTitle;
|
||||
private String mSubTitle;
|
||||
private boolean mExpanderPressed;
|
||||
|
||||
public MediaView(Context context)
|
||||
@ -327,7 +203,7 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
else
|
||||
expanderHeight = 0;
|
||||
|
||||
if (mSecondaryField != -1)
|
||||
if (mFields.length > 1)
|
||||
textHeight = (int)(7 * mTextSize / 2);
|
||||
else
|
||||
textHeight = (int)(2 * mTextSize);
|
||||
@ -344,7 +220,7 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
@Override
|
||||
public void onDraw(Canvas canvas)
|
||||
{
|
||||
if (mData == null)
|
||||
if (mTitle == null)
|
||||
return;
|
||||
|
||||
int width = getWidth();
|
||||
@ -364,33 +240,32 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
|
||||
float allocatedHeight;
|
||||
|
||||
if (mSecondaryField != -1) {
|
||||
if (mSubTitle != null) {
|
||||
allocatedHeight = height / 2 - padding * 3 / 2;
|
||||
|
||||
paint.setColor(Color.GRAY);
|
||||
canvas.drawText(mData.getField(mSecondaryField), padding, height / 2 + padding / 2 + (allocatedHeight - mTextSize) / 2 - paint.ascent(), paint);
|
||||
canvas.drawText(mSubTitle, padding, height / 2 + padding / 2 + (allocatedHeight - mTextSize) / 2 - paint.ascent(), paint);
|
||||
} else {
|
||||
allocatedHeight = height - padding * 2;
|
||||
}
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
canvas.drawText(mData.getField(mPrimaryField), padding, padding + (allocatedHeight - mTextSize) / 2 - paint.ascent(), paint);
|
||||
canvas.drawText(mTitle, padding, padding + (allocatedHeight - mTextSize) / 2 - paint.ascent(), paint);
|
||||
}
|
||||
|
||||
public final void updateMedia(SongData data)
|
||||
public final int getFieldCount()
|
||||
{
|
||||
mData = data;
|
||||
invalidate();
|
||||
return mFields.length;
|
||||
}
|
||||
|
||||
public final int getPrimaryField()
|
||||
public final long getMediaId()
|
||||
{
|
||||
return mPrimaryField;
|
||||
return mId;
|
||||
}
|
||||
|
||||
public final SongData getExpanderData()
|
||||
public final String getTitle()
|
||||
{
|
||||
return mData;
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public final boolean isExpanderPressed()
|
||||
@ -398,6 +273,33 @@ public class MediaAdapter extends BaseAdapter implements Filterable {
|
||||
return mExpanderPressed;
|
||||
}
|
||||
|
||||
public final void updateMedia(Cursor cursor)
|
||||
{
|
||||
mId = cursor.getLong(0);
|
||||
mTitle = cursor.getString(1);
|
||||
if (mFields.length > 1)
|
||||
mSubTitle = cursor.getString(2);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public final String[] getLimiter()
|
||||
{
|
||||
ContentResolver resolver = ContextApplication.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
|
@ -622,14 +622,14 @@ public class PlaybackService extends Service implements Runnable, MediaPlayer.On
|
||||
loadPreference((String)message.obj);
|
||||
break;
|
||||
case DO_ITEM:
|
||||
long id = message.obj == null ? -1 : ((Intent)message.obj).getLongExtra("id", -1);
|
||||
Intent intent = (Intent)message.obj;
|
||||
long id = message.obj == null ? -1 : intent.getLongExtra("id", -1);
|
||||
if (id == -1) {
|
||||
mQueuePos = 0;
|
||||
} else {
|
||||
Intent intent = (Intent)message.obj;
|
||||
boolean enqueue = intent.getIntExtra("action", ACTION_PLAY) == ACTION_ENQUEUE;
|
||||
|
||||
long[] songs = Song.getAllSongIdsWith(intent.getIntExtra("type", SongData.FIELD_TITLE), id);
|
||||
long[] songs = Song.getAllSongIdsWith(intent.getIntExtra("type", 3), id);
|
||||
if (songs == null || songs.length == 0)
|
||||
break;
|
||||
|
||||
|
@ -131,13 +131,16 @@ public class Song implements Parcelable {
|
||||
|
||||
public static long[] getAllSongIdsWith(int type, long id)
|
||||
{
|
||||
if (type == SongData.FIELD_TITLE)
|
||||
switch (type) {
|
||||
case 3:
|
||||
return new long[] { id };
|
||||
else if (type == SongData.FIELD_ALBUM)
|
||||
case 2:
|
||||
return Song.getAllSongIds(MediaStore.Audio.Media.ALBUM_ID + '=' + id);
|
||||
else if (type == SongData.FIELD_ARTIST)
|
||||
case 1:
|
||||
return Song.getAllSongIds(MediaStore.Audio.Media.ARTIST_ID + '=' + id);
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void randomize()
|
||||
|
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
|
||||
*
|
||||
* This file is part of Vanilla Music Player.
|
||||
*
|
||||
* Vanilla Music Player is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Vanilla Music Player is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.SparseArray;
|
||||
|
||||
public class SongData {
|
||||
public static final int FIELD_ARTIST = 1;
|
||||
public static final int FIELD_ALBUM = 2;
|
||||
public static final int FIELD_TITLE = 3;
|
||||
|
||||
public long id;
|
||||
public long albumId;
|
||||
public long artistId;
|
||||
|
||||
public String title;
|
||||
public String album;
|
||||
public String artist;
|
||||
|
||||
private SongData(Cursor cursor)
|
||||
{
|
||||
id = cursor.getLong(0);
|
||||
title = cursor.getString(1);
|
||||
albumId = cursor.getLong(2);
|
||||
album = cursor.getString(3);
|
||||
artistId = cursor.getLong(4);
|
||||
artist = cursor.getString(5);
|
||||
}
|
||||
|
||||
public String getField(int field)
|
||||
{
|
||||
switch (field) {
|
||||
case FIELD_TITLE:
|
||||
return title;
|
||||
case FIELD_ARTIST:
|
||||
return artist;
|
||||
case FIELD_ALBUM:
|
||||
return album;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getFieldId(int field)
|
||||
{
|
||||
switch (field) {
|
||||
case FIELD_TITLE:
|
||||
return id;
|
||||
case FIELD_ARTIST:
|
||||
return artistId;
|
||||
case FIELD_ALBUM:
|
||||
return albumId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static SongData[] getAllSongData()
|
||||
{
|
||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
String[] projection = { MediaStore.Audio.Media._ID,
|
||||
MediaStore.Audio.Media.TITLE,
|
||||
MediaStore.Audio.Media.ALBUM_ID,
|
||||
MediaStore.Audio.Media.ALBUM,
|
||||
MediaStore.Audio.Media.ARTIST_ID,
|
||||
MediaStore.Audio.Media.ARTIST };
|
||||
String selection = MediaStore.Audio.Media.IS_MUSIC + "!=0";
|
||||
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
Cursor cursor = resolver.query(media, projection, selection, null, null);
|
||||
|
||||
if (cursor == null)
|
||||
return null;
|
||||
|
||||
int count = cursor.getCount();
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
SongData[] songs = new SongData[count];
|
||||
while (--count != -1) {
|
||||
if (!cursor.moveToNext())
|
||||
return null;
|
||||
songs[count] = new SongData(cursor);
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
return songs;
|
||||
}
|
||||
|
||||
public static class TitleComparator implements Comparator<SongData> {
|
||||
public int compare(SongData a, SongData b)
|
||||
{
|
||||
return a.title.compareToIgnoreCase(b.title);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AlbumComparator implements IdComparator {
|
||||
public int compare(SongData a, SongData b)
|
||||
{
|
||||
return a.album.compareToIgnoreCase(b.album);
|
||||
}
|
||||
|
||||
public long getId(SongData song)
|
||||
{
|
||||
return song.albumId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArtistComparator implements IdComparator {
|
||||
public int compare(SongData a, SongData b)
|
||||
{
|
||||
return a.artist.compareToIgnoreCase(b.artist);
|
||||
}
|
||||
|
||||
public long getId(SongData song)
|
||||
{
|
||||
return song.artistId;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface IdComparator extends Comparator<SongData> {
|
||||
public long getId(SongData song);
|
||||
}
|
||||
|
||||
public static SongData[] filter(SongData[] songs, IdComparator comparator)
|
||||
{
|
||||
SparseArray<SongData> albums = new SparseArray<SongData>(songs.length);
|
||||
for (int i = songs.length; --i != -1; ) {
|
||||
SongData song = songs[i];
|
||||
int id = (int)comparator.getId(song);
|
||||
if (albums.get(id) == null)
|
||||
albums.put(id, song);
|
||||
}
|
||||
|
||||
SongData[] result = new SongData[albums.size()];
|
||||
for (int i = result.length; --i != -1; )
|
||||
result[i] = albums.valueAt(i);
|
||||
|
||||
Arrays.sort(result, comparator);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -18,8 +18,6 @@
|
||||
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.kreed.vanilla.R;
|
||||
|
||||
import android.app.Dialog;
|
||||
@ -29,9 +27,11 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.PaintDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
@ -71,12 +71,12 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
return (MediaAdapter)getList(tab).getAdapter();
|
||||
}
|
||||
|
||||
private void initializeList(int id, SongData[] songs, int lineA, int lineB, View.OnClickListener expanderListener)
|
||||
private void initializeList(int id, Uri store, String[] fields, String[] fieldKeys, View.OnClickListener expanderListener, String selection)
|
||||
{
|
||||
ListView view = (ListView)findViewById(id);
|
||||
view.setOnItemClickListener(this);
|
||||
view.setOnCreateContextMenuListener(this);
|
||||
view.setAdapter(new MediaAdapter(getContext(), songs, lineA, lineB, expanderListener));
|
||||
view.setAdapter(new MediaAdapter(getContext(), store, fields, fieldKeys, expanderListener, selection));
|
||||
}
|
||||
|
||||
public SongSelector(Context context)
|
||||
@ -109,12 +109,9 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
new Handler().post(new Runnable() {
|
||||
public void run()
|
||||
{
|
||||
SongData[] songs = SongData.getAllSongData();
|
||||
|
||||
initializeList(R.id.artist_list, SongData.filter(songs, new SongData.ArtistComparator()), SongData.FIELD_ARTIST, -1, SongSelector.this);
|
||||
initializeList(R.id.album_list, SongData.filter(songs, new SongData.AlbumComparator()), SongData.FIELD_ALBUM, SongData.FIELD_ARTIST, SongSelector.this);
|
||||
Arrays.sort(songs, new SongData.TitleComparator());
|
||||
initializeList(R.id.song_list, songs, SongData.FIELD_TITLE, SongData.FIELD_ARTIST, null);
|
||||
initializeList(R.id.artist_list, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, MediaAdapter.ARTIST_FIELDS, MediaAdapter.ARTIST_FIELD_KEYS, SongSelector.this, null);
|
||||
initializeList(R.id.album_list, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, MediaAdapter.ALBUM_FIELDS, MediaAdapter.ALBUM_FIELD_KEYS,SongSelector.this, null);
|
||||
initializeList(R.id.song_list, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaAdapter.SONG_FIELDS, MediaAdapter.SONG_FIELD_KEYS, null, MediaStore.Audio.Media.IS_MUSIC + "!=0");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -145,36 +142,32 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
return mTextFilter.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private void sendSongIntent(Intent intent)
|
||||
private void sendSongIntent(MediaAdapter.MediaView view, int action)
|
||||
{
|
||||
int action = intent.getIntExtra("action", PlaybackService.ACTION_PLAY);
|
||||
String title = intent.getStringExtra("title");
|
||||
intent.removeExtra("title");
|
||||
|
||||
int res = action == PlaybackService.ACTION_PLAY ? R.string.playing : R.string.enqueued;
|
||||
String text = getContext().getResources().getString(res, title);
|
||||
String text = getContext().getResources().getString(res, view.getTitle());
|
||||
Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show();
|
||||
|
||||
long id = view.getMediaId();
|
||||
int field = view.getFieldCount();
|
||||
|
||||
Intent intent = new Intent(getContext(), PlaybackService.class);
|
||||
intent.putExtra("type", field);
|
||||
intent.putExtra("action", action);
|
||||
intent.putExtra("id", id);
|
||||
getContext().startService(intent);
|
||||
|
||||
mLastActedId = intent.getIntExtra("id", -1);
|
||||
mLastActedId = id;
|
||||
}
|
||||
|
||||
private void expand(MediaAdapter.MediaView view)
|
||||
{
|
||||
int field = view.getPrimaryField();
|
||||
SongData data = view.getExpanderData();
|
||||
long limiter = MediaAdapter.makeLimiter(field, data);
|
||||
String[] limiter = view.getLimiter();
|
||||
|
||||
for (int i = field; i != 3; ++i) {
|
||||
MediaAdapter adapter = getAdapter(i);
|
||||
if (adapter.getLimiter() != limiter) {
|
||||
adapter.hideAll();
|
||||
adapter.setLimiter(limiter, data);
|
||||
}
|
||||
}
|
||||
for (int i = limiter.length; i != 3; ++i)
|
||||
getAdapter(i).setLimiter(limiter);
|
||||
|
||||
mTabHost.setCurrentTab(field);
|
||||
mTabHost.setCurrentTab(limiter.length);
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> list, View view, int pos, long id)
|
||||
@ -185,7 +178,7 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
else if (id == mLastActedId)
|
||||
dismiss();
|
||||
else
|
||||
sendSongIntent(((MediaAdapter)list.getAdapter()).buildSongIntent(mDefaultAction, pos));
|
||||
sendSongIntent(mediaView, mDefaultAction);
|
||||
}
|
||||
|
||||
public void afterTextChanged(Editable editable)
|
||||
@ -200,7 +193,7 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
{
|
||||
MediaAdapter adapter = getAdapter(mTabHost.getCurrentTab());
|
||||
if (adapter != null)
|
||||
adapter.getFilter().filter(text, this);
|
||||
adapter.filter(text, this);
|
||||
}
|
||||
|
||||
private void updateLimiterViews()
|
||||
@ -214,21 +207,20 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
if (adapter == null)
|
||||
return;
|
||||
|
||||
int field = adapter.getLimiterField();
|
||||
SongData data = adapter.getLimiterData();
|
||||
if (field == -1)
|
||||
String[] limiter = adapter.getLimiter();
|
||||
if (limiter == null)
|
||||
return;
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = 5;
|
||||
for (int i = SongData.FIELD_ARTIST; i <= field; ++i) {
|
||||
for (int i = 0; i != limiter.length; ++i) {
|
||||
PaintDrawable background = new PaintDrawable(Color.GRAY);
|
||||
background.setCornerRadius(5);
|
||||
|
||||
TextView view = new TextView(getContext());
|
||||
view.setSingleLine();
|
||||
view.setEllipsize(TextUtils.TruncateAt.MARQUEE);
|
||||
view.setText(data.getField(i) + " | X");
|
||||
view.setText(limiter[i] + " | X");
|
||||
view.setTextColor(Color.WHITE);
|
||||
view.setBackgroundDrawable(background);
|
||||
view.setLayoutParams(params);
|
||||
@ -249,14 +241,20 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
if (view == mClearButton) {
|
||||
mTextFilter.setText("");
|
||||
} else {
|
||||
int field = (Integer)view.getTag() - 1;
|
||||
SongData data = getAdapter(mTabHost.getCurrentTab()).getLimiterData();
|
||||
long limiter = field == 0 ? -1 : MediaAdapter.makeLimiter(field, data);
|
||||
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 i = 3; --i != -1; ) {
|
||||
MediaAdapter adapter = getAdapter(i);
|
||||
if (adapter.getLimiterField() > field)
|
||||
adapter.setLimiter(limiter, data);
|
||||
for (int j = 3; --j != -1; ) {
|
||||
MediaAdapter adapter = getAdapter(j);
|
||||
if (adapter.getLimiterLength() > i)
|
||||
adapter.setLimiter(limiter);
|
||||
}
|
||||
|
||||
updateLimiterViews();
|
||||
@ -268,29 +266,30 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
private static final int MENU_EXPAND = 2;
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info)
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo absInfo)
|
||||
{
|
||||
MediaAdapter adapter = (MediaAdapter)((ListView)view).getAdapter();
|
||||
int pos = (int)((AdapterView.AdapterContextMenuInfo)info).position;
|
||||
menu.add(0, MENU_PLAY, 0, R.string.play).setIntent(adapter.buildSongIntent(PlaybackService.ACTION_PLAY, pos));
|
||||
menu.add(0, MENU_ENQUEUE, 0, R.string.enqueue).setIntent(adapter.buildSongIntent(PlaybackService.ACTION_ENQUEUE, pos));
|
||||
if (adapter.hasExpanders())
|
||||
menu.add(0, MENU_PLAY, 0, R.string.play);
|
||||
menu.add(0, MENU_ENQUEUE, 0, R.string.enqueue);
|
||||
if (((MediaAdapter)((ListView)view).getAdapter()).hasExpanders())
|
||||
menu.add(0, MENU_EXPAND, 0, R.string.expand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item)
|
||||
{
|
||||
MediaAdapter.MediaView view = (MediaAdapter.MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView;
|
||||
int action = PlaybackService.ACTION_PLAY;
|
||||
switch (item.getItemId()) {
|
||||
case MENU_EXPAND:
|
||||
expand((MediaAdapter.MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView);
|
||||
expand(view);
|
||||
break;
|
||||
case MENU_PLAY:
|
||||
case MENU_ENQUEUE:
|
||||
Intent intent = item.getIntent();
|
||||
action = PlaybackService.ACTION_ENQUEUE;
|
||||
// fall through
|
||||
case MENU_PLAY:
|
||||
if (mDefaultIsLastAction)
|
||||
mDefaultAction = intent.getIntExtra("action", PlaybackService.ACTION_PLAY);
|
||||
sendSongIntent(intent);
|
||||
mDefaultAction = action;
|
||||
sendSongIntent(view, action);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@ -311,7 +310,7 @@ public class SongSelector extends Dialog implements AdapterView.OnItemClickListe
|
||||
{
|
||||
CharSequence text = mTextFilter.getText();
|
||||
for (int i = 3; --i != -1; )
|
||||
getAdapter(i).getFilter().filter(text);
|
||||
getAdapter(i).filter(text, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user