package org.music.player; import org.music.player.R; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.MediaStore; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.TextView; /** * CursorAdapter backed by MediaStore playlists. */ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback, DragListView.DragAdapter { private static final String[] PROJECTION = new String[] { MediaStore.Audio.Playlists.Members._ID, MediaStore.Audio.Playlists.Members.TITLE, MediaStore.Audio.Playlists.Members.ARTIST, MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Playlists.Members.PLAY_ORDER, }; private final Context mContext; private final Handler mWorkerHandler; private final Handler mUiHandler; private final LayoutInflater mInflater; private final Drawable mExpander; private long mPlaylistId; private boolean mEditable; /** * Create a playlist adapter. * * @param context A context to use. * @param worker A looper running a worker thread (to run queries on). */ public PlaylistAdapter(Context context, Looper worker) { super(context, null, false); mContext = context; mUiHandler = new Handler(this); mWorkerHandler = new Handler(worker, this); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mExpander = context.getResources().getDrawable(R.drawable.grabber); } /** * Set the id of the backing playlist. * * @param id The MediaStore id of a playlist. */ public void setPlaylistId(long id) { mPlaylistId = id; mWorkerHandler.sendEmptyMessage(MSG_RUN_QUERY); } /** * Enabled or disable edit mode. Edit mode adds a drag grabber to the left * side a views and a delete button to the right side of views. * * @param editable True to enable edit mode. */ public void setEditable(boolean editable) { mEditable = editable; notifyDataSetInvalidated(); } /** * Update the values in the given view. */ @Override public void bindView(View view, Context context, Cursor cursor) { TextView textView = (TextView)view; textView.setText(cursor.getString(1)); textView.setCompoundDrawablesWithIntrinsicBounds(mEditable ? mExpander : null, null, null, null); textView.setTag(cursor.getLong(3)); } /** * Generate a new view. */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.playlist_row, null); } /** * Re-run the query. Should be run on worker thread. */ public static final int MSG_RUN_QUERY = 1; /** * Update the cursor. Must be run on UI thread. */ public static final int MSG_UPDATE_CURSOR = 2; @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_RUN_QUERY: { Cursor cursor = runQuery(mContext.getContentResolver()); mUiHandler.sendMessage(mUiHandler.obtainMessage(MSG_UPDATE_CURSOR, cursor)); break; } case MSG_UPDATE_CURSOR: changeCursor((Cursor)message.obj); break; default: return false; } return true; } /** * Query the playlist songs. * * @param resolver A ContentResolver to query with. * @return The resulting cursor. */ private Cursor runQuery(ContentResolver resolver) { QueryTask query = MediaUtils.buildPlaylistQuery(mPlaylistId, PROJECTION, null); return query.runQuery(resolver); } @Override public void move(int from, int to) { if (from == to) // easy mode return; int count = getCount(); if (to >= count || from >= count) // this can happen when the adapter changes during the drag return; // The Android API contains a method to move a playlist item, however, // it has only been available since Froyo and doesn't seem to work // after a song has been removed from the playlist (I think?). ContentResolver resolver = mContext.getContentResolver(); Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId); Cursor cursor = getCursor(); int start = Math.min(from, to); int end = Math.max(from, to); long order; if (start == 0) { order = 0; } else { cursor.moveToPosition(start - 1); order = cursor.getLong(4) + 1; } cursor.moveToPosition(end); long endOrder = cursor.getLong(4); // clear the rows we are replacing String[] args = new String[] { Long.toString(order), Long.toString(endOrder) }; resolver.delete(uri, "play_order >= ? AND play_order <= ?", args); // create the new rows ContentValues[] values = new ContentValues[end - start + 1]; for (int i = start, j = 0; i <= end; ++i, ++j, ++order) { cursor.moveToPosition(i == to ? from : i > to ? i - 1 : i + 1); ContentValues value = new ContentValues(2); value.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Long.valueOf(order)); value.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, cursor.getLong(3)); values[j] = value; } // insert the new rows resolver.bulkInsert(uri, values); changeCursor(runQuery(resolver)); } @Override public void remove(int position) { ContentResolver resolver = mContext.getContentResolver(); Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId); resolver.delete(ContentUris.withAppendedId(uri, getItemId(position)), null, null); changeCursor(runQuery(resolver)); } }