auto import from //depot/cupcake/@135843
diff --git a/src/com/android/music/TrackBrowserActivity.java b/src/com/android/music/TrackBrowserActivity.java
new file mode 100644
index 0000000..58f556c
--- /dev/null
+++ b/src/com/android/music/TrackBrowserActivity.java
@@ -0,0 +1,1461 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.music;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.database.AbstractCursor;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaFile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Playlists;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AlphabetIndexer;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.text.Collator;
+import java.util.Arrays;
+
+public class TrackBrowserActivity extends ListActivity
+        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
+{
+    private final int Q_SELECTED = CHILD_MENU_BASE;
+    private final int Q_ALL = CHILD_MENU_BASE + 1;
+    private final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
+    private final int PLAY_ALL = CHILD_MENU_BASE + 3;
+    private final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
+    private final int REMOVE = CHILD_MENU_BASE + 5;
+    private final int SEARCH = CHILD_MENU_BASE + 6;
+
+
+    private static final String LOGTAG = "TrackBrowser";
+
+    private String[] mCursorCols;
+    private String[] mPlaylistMemberCols;
+    private boolean mDeletedOneRow = false;
+    private boolean mEditMode = false;
+    private String mCurrentTrackName;
+    private String mCurrentAlbumName;
+    private String mCurrentArtistNameForAlbum;
+    private ListView mTrackList;
+    private Cursor mTrackCursor;
+    private TrackListAdapter mAdapter;
+    private boolean mAdapterSent = false;
+    private String mAlbumId;
+    private String mArtistId;
+    private String mPlaylist;
+    private String mGenre;
+    private String mSortOrder;
+    private int mSelectedPosition;
+    private long mSelectedId;
+
+    public TrackBrowserActivity()
+    {
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle)
+    {
+        super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        if (icicle != null) {
+            mSelectedId = icicle.getLong("selectedtrack");
+            mAlbumId = icicle.getString("album");
+            mArtistId = icicle.getString("artist");
+            mPlaylist = icicle.getString("playlist");
+            mGenre = icicle.getString("genre");
+            mEditMode = icicle.getBoolean("editmode", false);
+        } else {
+            mAlbumId = getIntent().getStringExtra("album");
+            // If we have an album, show everything on the album, not just stuff
+            // by a particular artist.
+            Intent intent = getIntent();
+            mArtistId = intent.getStringExtra("artist");
+            mPlaylist = intent.getStringExtra("playlist");
+            mGenre = intent.getStringExtra("genre");
+            mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
+        }
+
+        mCursorCols = new String[] {
+                MediaStore.Audio.Media._ID,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.TITLE_KEY,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ARTIST_ID,
+                MediaStore.Audio.Media.DURATION
+        };
+        mPlaylistMemberCols = new String[] {
+                MediaStore.Audio.Playlists.Members._ID,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.TITLE_KEY,
+                MediaStore.Audio.Media.DATA,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ARTIST_ID,
+                MediaStore.Audio.Media.DURATION,
+                MediaStore.Audio.Playlists.Members.PLAY_ORDER,
+                MediaStore.Audio.Playlists.Members.AUDIO_ID
+        };
+
+        setContentView(R.layout.media_picker_activity);
+        mTrackList = getListView();
+        mTrackList.setOnCreateContextMenuListener(this);
+        if (mEditMode) {
+            //((TouchInterceptor) mTrackList).setDragListener(mDragListener);
+            ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
+            ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
+            mTrackList.setCacheColorHint(0);
+        } else {
+            mTrackList.setTextFilterEnabled(true);
+        }
+        mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
+        
+        if (mAdapter != null) {
+            mAdapter.setActivity(this);
+            setListAdapter(mAdapter);
+        }
+        MusicUtils.bindToService(this, this);
+    }
+
+    public void onServiceConnected(ComponentName name, IBinder service)
+    {
+        IntentFilter f = new IntentFilter();
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        f.addDataScheme("file");
+        registerReceiver(mScanListener, f);
+
+        if (mAdapter == null) {
+            //Log.i("@@@", "starting query");
+            mAdapter = new TrackListAdapter(
+                    getApplication(), // need to use application context to avoid leaks
+                    this,
+                    mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
+                    null, // cursor
+                    new String[] {},
+                    new int[] {},
+                    "nowplaying".equals(mPlaylist),
+                    mPlaylist != null &&
+                    !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded")));
+            setListAdapter(mAdapter);
+            setTitle(R.string.working_songs);
+            getTrackCursor(mAdapter.getQueryHandler(), null);
+        } else {
+            mTrackCursor = mAdapter.getCursor();
+            // If mTrackCursor is null, this can be because it doesn't have
+            // a cursor yet (because the initial query that sets its cursor
+            // is still in progress), or because the query failed.
+            // In order to not flash the error dialog at the user for the
+            // first case, simply retry the query when the cursor is null.
+            // Worst case, we end up doing the same query twice.
+            if (mTrackCursor != null) {
+                init(mTrackCursor);
+            } else {
+                setTitle(R.string.working_songs);
+                getTrackCursor(mAdapter.getQueryHandler(), null);
+            }
+        }
+    }
+    
+    public void onServiceDisconnected(ComponentName name) {
+        // we can't really function without the service, so don't
+        finish();
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        TrackListAdapter a = mAdapter;
+        mAdapterSent = true;
+        return a;
+    }
+    
+    @Override
+    public void onDestroy() {
+        MusicUtils.unbindFromService(this);
+        try {
+            if ("nowplaying".equals(mPlaylist)) {
+                unregisterReceiver(mNowPlayingListener);
+            } else {
+                unregisterReceiver(mTrackListListener);
+            }
+        } catch (IllegalArgumentException ex) {
+            // we end up here in case we never registered the listeners
+        }
+        
+        // if we didn't send the adapter off to another activity, we should
+        // close the cursor
+        if (!mAdapterSent) {
+            Cursor c = mAdapter.getCursor();
+            if (c != null) {
+                c.close();
+            }
+        }
+        unregisterReceiver(mScanListener);
+        super.onDestroy();
+   }
+    
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mTrackCursor != null) {
+            getListView().invalidateViews();
+        }
+        MusicUtils.setSpinnerState(this);
+    }
+    @Override
+    public void onPause() {
+        mReScanHandler.removeCallbacksAndMessages(null);
+        super.onPause();
+    }
+    
+    /*
+     * This listener gets called when the media scanner starts up or finishes, and
+     * when the sd card is unmounted.
+     */
+    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) ||
+                    Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
+                MusicUtils.setSpinnerState(TrackBrowserActivity.this);
+            }
+            mReScanHandler.sendEmptyMessage(0);
+        }
+    };
+    
+    private Handler mReScanHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            getTrackCursor(mAdapter.getQueryHandler(), null);
+            // if the query results in a null cursor, onQueryComplete() will
+            // call init(), which will post a delayed message to this handler
+            // in order to try again.
+        }
+    };
+    
+    public void onSaveInstanceState(Bundle outcicle) {
+        // need to store the selected item so we don't lose it in case
+        // of an orientation switch. Otherwise we could lose it while
+        // in the middle of specifying a playlist to add the item to.
+        outcicle.putLong("selectedtrack", mSelectedId);
+        outcicle.putString("artist", mArtistId);
+        outcicle.putString("album", mAlbumId);
+        outcicle.putString("playlist", mPlaylist);
+        outcicle.putString("genre", mGenre);
+        outcicle.putBoolean("editmode", mEditMode);
+        super.onSaveInstanceState(outcicle);
+    }
+    
+    public void init(Cursor newCursor) {
+
+        mAdapter.changeCursor(newCursor); // also sets mTrackCursor
+        
+        if (mTrackCursor == null) {
+            MusicUtils.displayDatabaseError(this);
+            closeContextMenu();
+            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
+            return;
+        }
+        
+        MusicUtils.hideDatabaseError(this);
+        setTitle();
+
+        // When showing the queue, position the selection on the currently playing track
+        // Otherwise, position the selection on the first matching artist, if any
+        IntentFilter f = new IntentFilter();
+        f.addAction(MediaPlaybackService.META_CHANGED);
+        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
+        if ("nowplaying".equals(mPlaylist)) {
+            try {
+                int cur = MusicUtils.sService.getQueuePosition();
+                setSelection(cur);
+                registerReceiver(mNowPlayingListener, new IntentFilter(f));
+                mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
+            } catch (RemoteException ex) {
+            }
+        } else {
+            String key = getIntent().getStringExtra("artist");
+            if (key != null) {
+                int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
+                mTrackCursor.moveToFirst();
+                while (! mTrackCursor.isAfterLast()) {
+                    String artist = mTrackCursor.getString(keyidx);
+                    if (artist.equals(key)) {
+                        setSelection(mTrackCursor.getPosition());
+                        break;
+                    }
+                    mTrackCursor.moveToNext();
+                }
+            }
+            registerReceiver(mTrackListListener, new IntentFilter(f));
+            mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
+        }
+    }
+
+    private void setTitle() {
+
+        CharSequence fancyName = null;
+        if (mAlbumId != null) {
+            int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
+            if (numresults > 0) {
+                mTrackCursor.moveToFirst();
+                int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
+                fancyName = mTrackCursor.getString(idx);
+                // For compilation albums show only the album title,
+                // but for regular albums show "artist - album".
+                // To determine whether something is a compilation
+                // album, do a query for the artist + album of the
+                // first item, and see if it returns the same number
+                // of results as the album query.
+                String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId +
+                        "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + 
+                        mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
+                                MediaStore.Audio.Media.ARTIST_ID));
+                Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
+                if (cursor != null) {
+                    if (cursor.getCount() != numresults) {
+                        // compilation album
+                        fancyName = mTrackCursor.getString(idx);
+                    }    
+                    cursor.deactivate();
+                }
+                if (fancyName.equals(MediaFile.UNKNOWN_STRING)) {
+                    fancyName = getString(R.string.unknown_album_name);
+                }
+            }
+        } else if (mPlaylist != null) {
+            if (mPlaylist.equals("nowplaying")) {
+                if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
+                    fancyName = getText(R.string.partyshuffle_title);
+                } else {
+                    fancyName = getText(R.string.nowplaying_title);
+                }
+            } else if (mPlaylist.equals("podcasts")){
+                fancyName = getText(R.string.podcasts_title);
+            } else if (mPlaylist.equals("recentlyadded")){
+                fancyName = getText(R.string.recentlyadded_title);
+            } else {
+                String [] cols = new String [] {
+                MediaStore.Audio.Playlists.NAME
+                };
+                Cursor cursor = MusicUtils.query(this,
+                        ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
+                        cols, null, null, null);
+                if (cursor != null) {
+                    if (cursor.getCount() != 0) {
+                        cursor.moveToFirst();
+                        fancyName = cursor.getString(0);
+                    }
+                    cursor.deactivate();
+                }
+            }
+        } else if (mGenre != null) {
+            String [] cols = new String [] {
+            MediaStore.Audio.Genres.NAME
+            };
+            Cursor cursor = MusicUtils.query(this,
+                    ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
+                    cols, null, null, null);
+            if (cursor != null) {
+                if (cursor.getCount() != 0) {
+                    cursor.moveToFirst();
+                    fancyName = cursor.getString(0);
+                }
+                cursor.deactivate();
+            }
+        }
+
+        if (fancyName != null) {
+            setTitle(fancyName);
+        } else {
+            setTitle(R.string.tracks_title);
+        }
+    }
+    
+    private TouchInterceptor.DragListener mDragListener =
+        new TouchInterceptor.DragListener() {
+        public void drag(int from, int to) {
+            if (mTrackCursor instanceof NowPlayingCursor) {
+                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+                c.moveItem(from, to);
+                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+                getListView().invalidateViews();
+                mDeletedOneRow = true;
+            }
+        }
+    };
+    private TouchInterceptor.DropListener mDropListener =
+        new TouchInterceptor.DropListener() {
+        public void drop(int from, int to) {
+            if (mTrackCursor instanceof NowPlayingCursor) {
+                // update the currently playing list
+                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+                c.moveItem(from, to);
+                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+                getListView().invalidateViews();
+                mDeletedOneRow = true;
+            } else {
+                // update a saved playlist
+                Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                        Long.valueOf(mPlaylist));
+                ContentValues values = new ContentValues();
+                String where = MediaStore.Audio.Playlists.Members._ID + "=?";
+                String [] wherearg = new String[1];
+                ContentResolver res = getContentResolver();
+                
+                int colidx = mTrackCursor.getColumnIndexOrThrow(
+                        MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+                if (from < to) {
+                    // move the item to somewhere later in the list
+                    mTrackCursor.moveToPosition(to);
+                    int toidx = mTrackCursor.getInt(colidx);
+                    mTrackCursor.moveToPosition(from);
+                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
+                    wherearg[0] = mTrackCursor.getString(0);
+                    res.update(baseUri, values, where, wherearg);
+                    for (int i = from + 1; i <= to; i++) {
+                        mTrackCursor.moveToPosition(i);
+                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i - 1);
+                        wherearg[0] = mTrackCursor.getString(0);
+                        res.update(baseUri, values, where, wherearg);
+                    }
+                } else if (from > to) {
+                    // move the item to somewhere earlier in the list
+                    mTrackCursor.moveToPosition(to);
+                    int toidx = mTrackCursor.getInt(colidx);
+                    mTrackCursor.moveToPosition(from);
+                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
+                    wherearg[0] = mTrackCursor.getString(0);
+                    res.update(baseUri, values, where, wherearg);
+                    for (int i = from - 1; i >= to; i--) {
+                        mTrackCursor.moveToPosition(i);
+                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i + 1);
+                        wherearg[0] = mTrackCursor.getString(0);
+                        res.update(baseUri, values, where, wherearg);
+                    }
+                }
+            }
+        }
+    };
+    
+    private TouchInterceptor.RemoveListener mRemoveListener =
+        new TouchInterceptor.RemoveListener() {
+        public void remove(int which) {
+            removePlaylistItem(which);
+        }
+    };
+
+    private void removePlaylistItem(int which) {
+        View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
+        try {
+            if (MusicUtils.sService != null
+                    && which != MusicUtils.sService.getQueuePosition()) {
+                mDeletedOneRow = true;
+            }
+        } catch (RemoteException e) {
+            // Service died, so nothing playing.
+            mDeletedOneRow = true;
+        }
+        v.setVisibility(View.GONE);
+        mTrackList.invalidateViews();
+        if (mTrackCursor instanceof NowPlayingCursor) {
+            ((NowPlayingCursor)mTrackCursor).removeItem(which);
+        } else {
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members._ID);
+            mTrackCursor.moveToPosition(which);
+            long id = mTrackCursor.getLong(colidx);
+            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            getContentResolver().delete(
+                    ContentUris.withAppendedId(uri, id), null, null);
+        }
+        v.setVisibility(View.VISIBLE);
+        mTrackList.invalidateViews();
+    }
+    
+    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            getListView().invalidateViews();
+        }
+    };
+
+    private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
+                getListView().invalidateViews();
+            } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
+                if (mDeletedOneRow) {
+                    // This is the notification for a single row that was
+                    // deleted previously, which is already reflected in
+                    // the UI.
+                    mDeletedOneRow = false;
+                    return;
+                }
+                Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+                if (c.getCount() == 0) {
+                    finish();
+                    return;
+                }
+                mAdapter.changeCursor(c);
+            }
+        }
+    };
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
+        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
+        MusicUtils.makePlaylistMenu(this, sub);
+        if (mEditMode) {
+            menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
+        }
+        menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
+        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
+        menu.add(0, SEARCH, 0, R.string.search_title);
+        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
+        mSelectedPosition =  mi.position;
+        mTrackCursor.moveToPosition(mSelectedPosition);
+        try {
+            int id_idx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members.AUDIO_ID);
+            mSelectedId = mTrackCursor.getInt(id_idx);
+        } catch (IllegalArgumentException ex) {
+            mSelectedId = mi.id;
+        }
+        mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.ALBUM));
+        mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.ARTIST));
+        mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
+                MediaStore.Audio.Media.TITLE));
+        menu.setHeaderTitle(mCurrentTrackName);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case PLAY_SELECTION: {
+                // play the track
+                int position = mSelectedPosition;
+                MusicUtils.playAll(this, mTrackCursor, position);
+                return true;
+            }
+
+            case QUEUE: {
+                int [] list = new int[] { (int) mSelectedId };
+                MusicUtils.addToCurrentPlaylist(this, list);
+                return true;
+            }
+
+            case NEW_PLAYLIST: {
+                Intent intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, NEW_PLAYLIST);
+                return true;
+            }
+
+            case PLAYLIST_SELECTED: {
+                int [] list = new int[] { (int) mSelectedId };
+                int playlist = item.getIntent().getIntExtra("playlist", 0);
+                MusicUtils.addToPlaylist(this, list, playlist);
+                return true;
+            }
+
+            case USE_AS_RINGTONE:
+                // Set the system setting to make this the current ringtone
+                MusicUtils.setRingtone(this, mSelectedId);
+                return true;
+
+            case DELETE_ITEM: {
+                int [] list = new int[1];
+                list[0] = (int) mSelectedId;
+                Bundle b = new Bundle();
+                String f = getString(R.string.delete_song_desc); 
+                String desc = String.format(f, mCurrentTrackName);
+                b.putString("description", desc);
+                b.putIntArray("items", list);
+                Intent intent = new Intent();
+                intent.setClass(this, DeleteItems.class);
+                intent.putExtras(b);
+                startActivityForResult(intent, -1);
+                return true;
+            }
+            
+            case REMOVE:
+                removePlaylistItem(mSelectedPosition);
+                return true;
+                
+            case SEARCH:
+                doSearch();
+                return true;
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    void doSearch() {
+        CharSequence title = null;
+        String query = null;
+        
+        Intent i = new Intent();
+        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        
+        title = mCurrentAlbumName;
+        query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
+        i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
+        i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
+        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
+        title = getString(R.string.mediasearch, title);
+        i.putExtra(SearchManager.QUERY, query);
+
+        startActivity(Intent.createChooser(i, title));
+    }
+
+    // In order to use alt-up/down as a shortcut for moving the selected item
+    // in the list, we need to override dispatchKeyEvent, not onKeyDown.
+    // (onKeyDown never sees these events, since they are handled by the list)
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mPlaylist != null && event.getMetaState() != 0 &&
+                event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    moveItem(true);
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    moveItem(false);
+                    return true;
+                case KeyEvent.KEYCODE_DEL:
+                    removeItem();
+                    return true;
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    private void removeItem() {
+        int curcount = mTrackCursor.getCount();
+        int curpos = mTrackList.getSelectedItemPosition();
+        if (curcount == 0 || curpos < 0) {
+            return;
+        }
+        
+        if ("nowplaying".equals(mPlaylist)) {
+            // remove track from queue
+
+            // Work around bug 902971. To get quick visual feedback
+            // of the deletion of the item, hide the selected view.
+            try {
+                if (curpos != MusicUtils.sService.getQueuePosition()) {
+                    mDeletedOneRow = true;
+                }
+            } catch (RemoteException ex) {
+            }
+            View v = mTrackList.getSelectedView();
+            v.setVisibility(View.GONE);
+            mTrackList.invalidateViews();
+            ((NowPlayingCursor)mTrackCursor).removeItem(curpos);
+            v.setVisibility(View.VISIBLE);
+            mTrackList.invalidateViews();
+        } else {
+            // remove track from playlist
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members._ID);
+            mTrackCursor.moveToPosition(curpos);
+            long id = mTrackCursor.getLong(colidx);
+            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            getContentResolver().delete(
+                    ContentUris.withAppendedId(uri, id), null, null);
+            curcount--;
+            if (curcount == 0) {
+                finish();
+            } else {
+                mTrackList.setSelection(curpos < curcount ? curpos : curcount);
+            }
+        }
+    }
+    
+    private void moveItem(boolean up) {
+        int curcount = mTrackCursor.getCount(); 
+        int curpos = mTrackList.getSelectedItemPosition();
+        if ( (up && curpos < 1) || (!up  && curpos >= curcount - 1)) {
+            return;
+        }
+
+        if (mTrackCursor instanceof NowPlayingCursor) {
+            NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
+            c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
+            ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
+            getListView().invalidateViews();
+            mDeletedOneRow = true;
+            if (up) {
+                mTrackList.setSelection(curpos - 1);
+            } else {
+                mTrackList.setSelection(curpos + 1);
+            }
+        } else {
+            int colidx = mTrackCursor.getColumnIndexOrThrow(
+                    MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+            mTrackCursor.moveToPosition(curpos);
+            int currentplayidx = mTrackCursor.getInt(colidx);
+            Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+                    Long.valueOf(mPlaylist));
+            ContentValues values = new ContentValues();
+            String where = MediaStore.Audio.Playlists.Members._ID + "=?";
+            String [] wherearg = new String[1];
+            ContentResolver res = getContentResolver();
+            if (up) {
+                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
+                wherearg[0] = mTrackCursor.getString(0);
+                res.update(baseUri, values, where, wherearg);
+                mTrackCursor.moveToPrevious();
+            } else {
+                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
+                wherearg[0] = mTrackCursor.getString(0);
+                res.update(baseUri, values, where, wherearg);
+                mTrackCursor.moveToNext();
+            }
+            values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
+            wherearg[0] = mTrackCursor.getString(0);
+            res.update(baseUri, values, where, wherearg);
+        }
+    }
+    
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id)
+    {
+        if (mTrackCursor.getCount() == 0) {
+            return;
+        }
+        MusicUtils.playAll(this, mTrackCursor, position);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        /* This activity is used for a number of different browsing modes, and the menu can
+         * be different for each of them:
+         * - all tracks, optionally restricted to an album, artist or playlist
+         * - the list of currently playing songs
+         */
+        super.onCreateOptionsMenu(menu);
+        if (mPlaylist == null) {
+            menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(com.android.internal.R.drawable.ic_menu_play_clip);
+        }
+        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
+        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback)
+                .setVisible(MusicUtils.isMusicLoaded());
+        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
+        if (mPlaylist != null) {
+            menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save);
+            if (mPlaylist.equals("nowplaying")) {
+                menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(com.android.internal.R.drawable.ic_menu_clear_playlist);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Intent intent;
+        Cursor cursor;
+        switch (item.getItemId()) {
+            case PLAY_ALL: {
+                MusicUtils.playAll(this, mTrackCursor);
+                return true;
+            }
+
+            case GOTO_START:
+                intent = new Intent();
+                intent.setClass(this, MusicBrowserActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+
+            case GOTO_PLAYBACK:
+                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                return true;
+                
+            case SHUFFLE_ALL:
+                // Should 'shuffle all' shuffle ALL, or only the tracks shown?
+                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        new String [] { MediaStore.Audio.Media._ID}, 
+                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                if (cursor != null) {
+                    MusicUtils.shuffleAll(this, cursor);
+                    cursor.close();
+                }
+                return true;
+                
+            case SAVE_AS_PLAYLIST:
+                intent = new Intent();
+                intent.setClass(this, CreatePlaylist.class);
+                startActivityForResult(intent, SAVE_AS_PLAYLIST);
+                return true;
+                
+            case CLEAR_PLAYLIST:
+                // We only clear the current playlist
+                MusicUtils.clearQueue();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (requestCode) {
+            case SCAN_DONE:
+                if (resultCode == RESULT_CANCELED) {
+                    finish();
+                } else {
+                    getTrackCursor(mAdapter.getQueryHandler(), null);
+                }
+                break;
+                
+            case NEW_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = new int[] { (int) mSelectedId };
+                        MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
+                    }
+                }
+                break;
+
+            case SAVE_AS_PLAYLIST:
+                if (resultCode == RESULT_OK) {
+                    Uri uri = intent.getData();
+                    if (uri != null) {
+                        int [] list = MusicUtils.getSongListForCursor(mTrackCursor);
+                        int plid = Integer.parseInt(uri.getLastPathSegment());
+                        MusicUtils.addToPlaylist(this, list, plid);
+                    }
+                }
+                break;
+        }
+    }
+    
+    private Cursor getTrackCursor(AsyncQueryHandler async, String filter) {
+        Cursor ret = null;
+        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
+        StringBuilder where = new StringBuilder();
+        where.append(MediaStore.Audio.Media.TITLE + " != ''");
+        
+        // Add in the filtering constraints
+        String [] keywords = null;
+        if (filter != null) {
+            String [] searchWords = filter.split(" ");
+            keywords = new String[searchWords.length];
+            Collator col = Collator.getInstance();
+            col.setStrength(Collator.PRIMARY);
+            for (int i = 0; i < searchWords.length; i++) {
+                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
+            }
+            for (int i = 0; i < searchWords.length; i++) {
+                where.append(" AND ");
+                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
+                where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
+                where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
+            }
+        }
+        
+        if (mGenre != null) {
+            mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Genres.Members.getContentUri("external",
+                        Integer.valueOf(mGenre)),
+                        mCursorCols, where.toString(), keywords, mSortOrder);
+                ret = null;
+            } else {
+                ret = MusicUtils.query(this,
+                        MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)),
+                        mCursorCols, where.toString(), keywords, mSortOrder);
+            }
+        } else if (mPlaylist != null) {
+            if (mPlaylist.equals("nowplaying")) {
+                if (MusicUtils.sService != null) {
+                    ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
+                    if (ret.getCount() == 0) {
+                        finish();
+                    }
+                } else {
+                    // Nothing is playing.
+                }
+            } else if (mPlaylist.equals("podcasts")) {
+                where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1");
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                    ret = null;
+                 } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                }
+            } else if (mPlaylist.equals("recentlyadded")) {
+                // do a query for all songs added in the last X weeks
+                int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
+                where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
+                where.append(System.currentTimeMillis() / 1000 - X);
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                    ret = null;
+                 } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols,
+                            where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+                }
+            } else {
+                mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
+                if (async != null) {
+                    async.startQuery(0, null,
+                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
+                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
+                    ret = null;
+                } else {
+                    ret = MusicUtils.query(this,
+                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
+                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
+                }
+            }
+        } else {
+            if (mAlbumId != null) {
+                where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
+                mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
+            }
+            if (mArtistId != null) {
+                where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId);
+            }
+            where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
+            if (async != null) {
+                async.startQuery(0, null,
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString() , keywords, mSortOrder);
+                ret = null;
+            } else {
+                ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString() , keywords, mSortOrder);
+            }
+        }
+        
+        // This special case is for the "nowplaying" cursor, which cannot be handled
+        // asynchronously using AsyncQueryHandler, so we do some extra initialization here.
+        if (ret != null && async != null) {
+            init(ret);
+            setTitle();
+        }
+        return ret;
+    }
+
+    private class NowPlayingCursor extends AbstractCursor
+    {
+        public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
+        {
+            mCols = cols;
+            mService  = service;
+            makeNowPlayingCursor();
+        }
+        private void makeNowPlayingCursor() {
+            mCurrentPlaylistCursor = null;
+            try {
+                mNowPlaying = mService.getQueue();
+            } catch (RemoteException ex) {
+                mNowPlaying = new int[0];
+            }
+            mSize = mNowPlaying.length;
+            if (mSize == 0) {
+                return;
+            }
+
+            StringBuilder where = new StringBuilder();
+            where.append(MediaStore.Audio.Media._ID + " IN (");
+            for (int i = 0; i < mSize; i++) {
+                where.append(mNowPlaying[i]);
+                if (i < mSize - 1) {
+                    where.append(",");
+                }
+            }
+            where.append(")");
+
+            mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
+                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    mCols, where.toString(), null, MediaStore.Audio.Media._ID);
+
+            if (mCurrentPlaylistCursor == null) {
+                mSize = 0;
+                return;
+            }
+            
+            int size = mCurrentPlaylistCursor.getCount();
+            mCursorIdxs = new int[size];
+            mCurrentPlaylistCursor.moveToFirst();
+            int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+            for (int i = 0; i < size; i++) {
+                mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx);
+                mCurrentPlaylistCursor.moveToNext();
+            }
+            mCurrentPlaylistCursor.moveToFirst();
+            mCurPos = -1;
+            
+            // At this point we can verify the 'now playing' list we got
+            // earlier to make sure that all the items in there still exist
+            // in the database, and remove those that aren't. This way we
+            // don't get any blank items in the list.
+            try {
+                int removed = 0;
+                for (int i = mNowPlaying.length - 1; i >= 0; i--) {
+                    int trackid = mNowPlaying[i];
+                    int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
+                    if (crsridx < 0) {
+                        //Log.i("@@@@@", "item no longer exists in db: " + trackid);
+                        removed += mService.removeTrack(trackid);
+                    }
+                }
+                if (removed > 0) {
+                    mNowPlaying = mService.getQueue();
+                    mSize = mNowPlaying.length;
+                    if (mSize == 0) {
+                        mCursorIdxs = null;
+                        return;
+                    }
+                }
+            } catch (RemoteException ex) {
+                mNowPlaying = new int[0];
+            }
+        }
+
+        @Override
+        public int getCount()
+        {
+            return mSize;
+        }
+
+        @Override
+        public boolean onMove(int oldPosition, int newPosition)
+        {
+            if (oldPosition == newPosition)
+                return true;
+            
+            if (mNowPlaying == null || mCursorIdxs == null) {
+                return false;
+            }
+
+            // The cursor doesn't have any duplicates in it, and is not ordered
+            // in queue-order, so we need to figure out where in the cursor we
+            // should be.
+           
+            int newid = mNowPlaying[newPosition];
+            int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
+            mCurrentPlaylistCursor.moveToPosition(crsridx);
+            mCurPos = newPosition;
+            
+            return true;
+        }
+
+        public boolean removeItem(int which)
+        {
+            try {
+                if (mService.removeTracks(which, which) == 0) {
+                    return false; // delete failed
+                }
+                int i = (int) which;
+                mSize--;
+                while (i < mSize) {
+                    mNowPlaying[i] = mNowPlaying[i+1];
+                    i++;
+                }
+                onMove(-1, (int) mCurPos);
+            } catch (RemoteException ex) {
+            }
+            return true;
+        }
+        
+        public void moveItem(int from, int to) {
+            try {
+                mService.moveQueueItem(from, to);
+                mNowPlaying = mService.getQueue();
+                onMove(-1, mCurPos); // update the underlying cursor
+            } catch (RemoteException ex) {
+            }
+        }
+
+        private void dump() {
+            String where = "(";
+            for (int i = 0; i < mSize; i++) {
+                where += mNowPlaying[i];
+                if (i < mSize - 1) {
+                    where += ",";
+                }
+            }
+            where += ")";
+            Log.i("NowPlayingCursor: ", where);
+        }
+
+        @Override
+        public String getString(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getString(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return "";
+            }
+        }
+
+        @Override
+        public short getShort(int column)
+        {
+            return mCurrentPlaylistCursor.getShort(column);
+        }
+
+        @Override
+        public int getInt(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getInt(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return 0;
+            }
+        }
+
+        @Override
+        public long getLong(int column)
+        {
+            try {
+                return mCurrentPlaylistCursor.getLong(column);
+            } catch (Exception ex) {
+                onChange(true);
+                return 0;
+            }
+        }
+
+        @Override
+        public float getFloat(int column)
+        {
+            return mCurrentPlaylistCursor.getFloat(column);
+        }
+
+        @Override
+        public double getDouble(int column)
+        {
+            return mCurrentPlaylistCursor.getDouble(column);
+        }
+
+        @Override
+        public boolean isNull(int column)
+        {
+            return mCurrentPlaylistCursor.isNull(column);
+        }
+
+        @Override
+        public String[] getColumnNames()
+        {
+            return mCols;
+        }
+        
+        @Override
+        public void deactivate()
+        {
+            if (mCurrentPlaylistCursor != null)
+                mCurrentPlaylistCursor.deactivate();
+        }
+
+        @Override
+        public boolean requery()
+        {
+            makeNowPlayingCursor();
+            return true;
+        }
+
+        private String [] mCols;
+        private Cursor mCurrentPlaylistCursor;     // updated in onMove
+        private int mSize;          // size of the queue
+        private int[] mNowPlaying;
+        private int[] mCursorIdxs;
+        private int mCurPos;
+        private IMediaPlaybackService mService;
+    }
+    
+    static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
+        boolean mIsNowPlaying;
+        boolean mDisableNowPlayingIndicator;
+
+        int mTitleIdx;
+        int mArtistIdx;
+        int mAlbumIdx;
+        int mDurationIdx;
+        int mAudioIdIdx;
+
+        private final StringBuilder mBuilder = new StringBuilder();
+        private final String mUnknownArtist;
+        private final String mUnknownAlbum;
+        
+        private AlphabetIndexer mIndexer;
+        
+        private TrackBrowserActivity mActivity = null;
+        private AsyncQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
+        
+        class ViewHolder {
+            TextView line1;
+            TextView line2;
+            TextView duration;
+            ImageView play_indicator;
+            CharArrayBuffer buffer1;
+            char [] buffer2;
+        }
+
+        class QueryHandler extends AsyncQueryHandler {
+            QueryHandler(ContentResolver res) {
+                super(res);
+            }
+            
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
+                mActivity.init(cursor);
+            }
+        }
+        
+        TrackListAdapter(Context context, TrackBrowserActivity currentactivity,
+                int layout, Cursor cursor, String[] from, int[] to,
+                boolean isnowplaying, boolean disablenowplayingindicator) {
+            super(context, layout, cursor, from, to);
+            mActivity = currentactivity;
+            getColumnIndices(cursor);
+            mIsNowPlaying = isnowplaying;
+            mDisableNowPlayingIndicator = disablenowplayingindicator;
+            mUnknownArtist = context.getString(R.string.unknown_artist_name);
+            mUnknownAlbum = context.getString(R.string.unknown_album_name);
+            
+            mQueryHandler = new QueryHandler(context.getContentResolver());
+        }
+        
+        public void setActivity(TrackBrowserActivity newactivity) {
+            mActivity = newactivity;
+        }
+        
+        public AsyncQueryHandler getQueryHandler() {
+            return mQueryHandler;
+        }
+        
+        private void getColumnIndices(Cursor cursor) {
+            if (cursor != null) {
+                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
+                mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
+                mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
+                mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
+                try {
+                    mAudioIdIdx = cursor.getColumnIndexOrThrow(
+                            MediaStore.Audio.Playlists.Members.AUDIO_ID);
+                } catch (IllegalArgumentException ex) {
+                    mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
+                }
+                
+                if (mIndexer != null) {
+                    mIndexer.setCursor(cursor);
+                } else if (!mActivity.mEditMode) {
+                    String alpha = mActivity.getString(
+                            com.android.internal.R.string.fast_scroll_alphabet);
+                
+                    mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
+                }
+            }
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            View v = super.newView(context, cursor, parent);
+            ImageView iv = (ImageView) v.findViewById(R.id.icon);
+            if (mActivity.mEditMode) {
+                iv.setVisibility(View.VISIBLE);
+                iv.setImageResource(R.drawable.ic_mp_move);
+            } else {
+                iv.setVisibility(View.GONE);
+            }
+            
+            ViewHolder vh = new ViewHolder();
+            vh.line1 = (TextView) v.findViewById(R.id.line1);
+            vh.line2 = (TextView) v.findViewById(R.id.line2);
+            vh.duration = (TextView) v.findViewById(R.id.duration);
+            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
+            vh.buffer1 = new CharArrayBuffer(100);
+            vh.buffer2 = new char[200];
+            v.setTag(vh);
+            return v;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            
+            ViewHolder vh = (ViewHolder) view.getTag();
+            
+            cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
+            vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
+            
+            int secs = cursor.getInt(mDurationIdx) / 1000;
+            if (secs == 0) {
+                vh.duration.setText("");
+            } else {
+                vh.duration.setText(MusicUtils.makeTimeString(context, secs));
+            }
+            
+            final StringBuilder builder = mBuilder;
+            builder.delete(0, builder.length());
+
+            String name = cursor.getString(mArtistIdx);
+            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
+                builder.append(mUnknownArtist);
+            } else {
+                builder.append(name);
+            }
+            int len = builder.length();
+            if (vh.buffer2.length < len) {
+                vh.buffer2 = new char[len];
+            }
+            builder.getChars(0, len, vh.buffer2, 0);
+            vh.line2.setText(vh.buffer2, 0, len);
+
+            ImageView iv = vh.play_indicator;
+            int id = -1;
+            if (MusicUtils.sService != null) {
+                // TODO: IPC call on each bind??
+                try {
+                    if (mIsNowPlaying) {
+                        id = MusicUtils.sService.getQueuePosition();
+                    } else {
+                        id = MusicUtils.sService.getAudioId();
+                    }
+                } catch (RemoteException ex) {
+                }
+            }
+            
+            // Determining whether and where to show the "now playing indicator
+            // is tricky, because we don't actually keep track of where the songs
+            // in the current playlist came from after they've started playing.
+            //
+            // If the "current playlists" is shown, then we can simply match by position,
+            // otherwise, we need to match by id. Match-by-id gets a little weird if
+            // a song appears in a playlist more than once, and you're in edit-playlist
+            // mode. In that case, both items will have the "now playing" indicator.
+            // For this reason, we don't show the play indicator at all when in edit
+            // playlist mode (except when you're viewing the "current playlist",
+            // which is not really a playlist)
+            if ( (mIsNowPlaying && cursor.getPosition() == id) ||
+                 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getInt(mAudioIdIdx) == id)) {
+                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
+                iv.setVisibility(View.VISIBLE);
+            } else {
+                iv.setVisibility(View.GONE);
+            }
+        }
+        
+        @Override
+        public void changeCursor(Cursor cursor) {
+            if (cursor != mActivity.mTrackCursor) {
+                mActivity.mTrackCursor = cursor;
+                super.changeCursor(cursor);
+                getColumnIndices(cursor);
+            }
+        }
+        
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getTrackCursor(null, s);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
+        }
+        
+        // SectionIndexer methods
+        
+        public Object[] getSections() {
+            if (mIndexer != null) { 
+                return mIndexer.getSections();
+            } else {
+                return null;
+            }
+        }
+        
+        public int getPositionForSection(int section) {
+            int pos = mIndexer.getPositionForSection(section);
+            return pos;
+        }
+        
+        public int getSectionForPosition(int position) {
+            return 0;
+        }        
+    }
+}
+