OSDN Git Service

Show album art in the background of the song list for an album.
[android-x86/packages-apps-Music.git] / src / com / android / music / TrackBrowserActivity.java
index 389bb1d..f972892 100644 (file)
@@ -31,6 +31,7 @@ import android.content.ServiceConnection;
 import android.database.AbstractCursor;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
+import android.graphics.Bitmap;
 import android.media.AudioManager;
 import android.media.MediaFile;
 import android.net.Uri;
@@ -65,13 +66,13 @@ 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 int Q_SELECTED = CHILD_MENU_BASE;
+    private static final int Q_ALL = CHILD_MENU_BASE + 1;
+    private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
+    private static final int PLAY_ALL = CHILD_MENU_BASE + 3;
+    private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
+    private static final int REMOVE = CHILD_MENU_BASE + 5;
+    private static final int SEARCH = CHILD_MENU_BASE + 6;
 
 
     private static final String LOGTAG = "TrackBrowser";
@@ -94,6 +95,9 @@ public class TrackBrowserActivity extends ListActivity
     private String mSortOrder;
     private int mSelectedPosition;
     private long mSelectedId;
+    private static int mLastListPosCourse = -1;
+    private static int mLastListPosFine = -1;
+    private boolean mUseLastListPos = false;
 
     public TrackBrowserActivity()
     {
@@ -105,6 +109,12 @@ public class TrackBrowserActivity extends ListActivity
     {
         super.onCreate(icicle);
         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        Intent intent = getIntent();
+        if (intent != null) {
+            if (intent.getBooleanExtra("withtabs", false)) {
+                requestWindowFeature(Window.FEATURE_NO_TITLE);
+            }
+        }
         setVolumeControlStream(AudioManager.STREAM_MUSIC);
         if (icicle != null) {
             mSelectedId = icicle.getLong("selectedtrack");
@@ -114,10 +124,9 @@ public class TrackBrowserActivity extends ListActivity
             mGenre = icicle.getString("genre");
             mEditMode = icicle.getBoolean("editmode", false);
         } else {
-            mAlbumId = getIntent().getStringExtra("album");
+            mAlbumId = intent.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");
@@ -144,10 +153,36 @@ public class TrackBrowserActivity extends ListActivity
                 MediaStore.Audio.Media.ARTIST_ID,
                 MediaStore.Audio.Media.DURATION,
                 MediaStore.Audio.Playlists.Members.PLAY_ORDER,
-                MediaStore.Audio.Playlists.Members.AUDIO_ID
+                MediaStore.Audio.Playlists.Members.AUDIO_ID,
+                MediaStore.Audio.Media.IS_MUSIC
         };
 
+        setContentView(R.layout.media_picker_activity);
+        mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
+        mTrackList = getListView();
+        mTrackList.setOnCreateContextMenuListener(this);
+        if (mEditMode) {
+            ((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);
+
+        // don't set the album art until after the view has been layed out
+        mTrackList.post(new Runnable() {
+
+            public void run() {
+                setAlbumArtBackground();
+            }
+        });
     }
 
     public void onServiceConnected(ComponentName name, IBinder service)
@@ -159,18 +194,6 @@ public class TrackBrowserActivity extends ListActivity
         f.addDataScheme("file");
         registerReceiver(mScanListener, f);
 
-        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) {
             //Log.i("@@@", "starting query");
             mAdapter = new TrackListAdapter(
@@ -185,10 +208,8 @@ public class TrackBrowserActivity extends ListActivity
                     !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded")));
             setListAdapter(mAdapter);
             setTitle(R.string.working_songs);
-            getTrackCursor(mAdapter.getQueryHandler(), null);
+            getTrackCursor(mAdapter.getQueryHandler(), null, true);
         } else {
-            mAdapter.setActivity(this);
-            setListAdapter(mAdapter);
             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
@@ -197,12 +218,15 @@ public class TrackBrowserActivity extends ListActivity
             // 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);
+                init(mTrackCursor, false);
             } else {
                 setTitle(R.string.working_songs);
-                getTrackCursor(mAdapter.getQueryHandler(), null);
+                getTrackCursor(mAdapter.getQueryHandler(), null, true);
             }
         }
+        if (!mEditMode) {
+            MusicUtils.updateNowPlaying(this);
+        }
     }
     
     public void onServiceDisconnected(ComponentName name) {
@@ -219,12 +243,20 @@ public class TrackBrowserActivity extends ListActivity
     
     @Override
     public void onDestroy() {
+        ListView lv = getListView();
+        if (lv != null && mUseLastListPos) {
+            mLastListPosCourse = lv.getFirstVisiblePosition();
+            View cv = lv.getChildAt(0);
+            if (cv != null) {
+                mLastListPosFine = cv.getTop();
+            }
+        }
         MusicUtils.unbindFromService(this);
         try {
             if ("nowplaying".equals(mPlaylist)) {
-                unregisterReceiver(mNowPlayingListener);
+                unregisterReceiverSafe(mNowPlayingListener);
             } else {
-                unregisterReceiver(mTrackListListener);
+                unregisterReceiverSafe(mTrackListListener);
             }
         } catch (IllegalArgumentException ex) {
             // we end up here in case we never registered the listeners
@@ -238,9 +270,28 @@ public class TrackBrowserActivity extends ListActivity
                 c.close();
             }
         }
-        unregisterReceiver(mScanListener);
+        // Because we pass the adapter to the next activity, we need to make
+        // sure it doesn't keep a reference to this activity. We can do this
+        // by clearing its DatasetObservers, which setListAdapter(null) does.
+        setListAdapter(null);
+        mAdapter = null;
+        unregisterReceiverSafe(mScanListener);
         super.onDestroy();
-   }
+    }
+    
+    /**
+     * Unregister a receiver, but eat the exception that is thrown if the
+     * receiver was never registered to begin with. This is a little easier
+     * than keeping track of whether the receivers have actually been
+     * registered by the time onDestroy() is called.
+     */
+    private void unregisterReceiverSafe(BroadcastReceiver receiver) {
+        try {
+            unregisterReceiver(receiver);
+        } catch (IllegalArgumentException e) {
+            // ignore
+        }
+    }
     
     @Override
     public void onResume() {
@@ -275,7 +326,9 @@ public class TrackBrowserActivity extends ListActivity
     private Handler mReScanHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            getTrackCursor(mAdapter.getQueryHandler(), null);
+            if (mAdapter != null) {
+                getTrackCursor(mAdapter.getQueryHandler(), null, true);
+            }
             // 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.
@@ -295,8 +348,11 @@ public class TrackBrowserActivity extends ListActivity
         super.onSaveInstanceState(outcicle);
     }
     
-    public void init(Cursor newCursor) {
+    public void init(Cursor newCursor, boolean isLimited) {
 
+        if (mAdapter == null) {
+            return;
+        }
         mAdapter.changeCursor(newCursor); // also sets mTrackCursor
         
         if (mTrackCursor == null) {
@@ -305,10 +361,23 @@ public class TrackBrowserActivity extends ListActivity
             mReScanHandler.sendEmptyMessageDelayed(0, 1000);
             return;
         }
-        
+
         MusicUtils.hideDatabaseError(this);
+        mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
         setTitle();
 
+        // Restore previous position
+        if (mLastListPosCourse >= 0 && mUseLastListPos) {
+            ListView lv = getListView();
+            // this hack is needed because otherwise the position doesn't change
+            // for the 2nd (non-limited) cursor
+            lv.setAdapter(lv.getAdapter());
+            lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
+            if (!isLimited) {
+                mLastListPosCourse = -1;
+            }
+        }
+
         // 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();
@@ -341,6 +410,21 @@ public class TrackBrowserActivity extends ListActivity
         }
     }
 
+    private void setAlbumArtBackground() {
+        try {
+            long albumid = Long.valueOf(mAlbumId);
+            Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false);
+            if (bm != null) {
+                MusicUtils.setBackground(mTrackList, bm);
+                mTrackList.setCacheColorHint(0);
+                return;
+            }
+        } catch (Exception ex) {
+        }
+        mTrackList.setBackgroundResource(0);
+        mTrackList.setCacheColorHint(0xff000000);
+    }
+
     private void setTitle() {
 
         CharSequence fancyName = null;
@@ -369,7 +453,7 @@ public class TrackBrowserActivity extends ListActivity
                     }    
                     cursor.deactivate();
                 }
-                if (fancyName.equals(MediaFile.UNKNOWN_STRING)) {
+                if (fancyName == null || fancyName.equals(MediaFile.UNKNOWN_STRING)) {
                     fancyName = getString(R.string.unknown_album_name);
                 }
             }
@@ -422,18 +506,6 @@ public class TrackBrowserActivity extends ListActivity
         }
     }
     
-    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) {
@@ -458,7 +530,7 @@ public class TrackBrowserActivity extends ListActivity
                 if (from < to) {
                     // move the item to somewhere later in the list
                     mTrackCursor.moveToPosition(to);
-                    int toidx = mTrackCursor.getInt(colidx);
+                    long toidx = mTrackCursor.getLong(colidx);
                     mTrackCursor.moveToPosition(from);
                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
                     wherearg[0] = mTrackCursor.getString(0);
@@ -472,7 +544,7 @@ public class TrackBrowserActivity extends ListActivity
                 } else if (from > to) {
                     // move the item to somewhere earlier in the list
                     mTrackCursor.moveToPosition(to);
-                    int toidx = mTrackCursor.getInt(colidx);
+                    long toidx = mTrackCursor.getLong(colidx);
                     mTrackCursor.moveToPosition(from);
                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
                     wherearg[0] = mTrackCursor.getString(0);
@@ -528,6 +600,9 @@ public class TrackBrowserActivity extends ListActivity
         @Override
         public void onReceive(Context context, Intent intent) {
             getListView().invalidateViews();
+            if (!mEditMode) {
+                MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
+            }
         }
     };
 
@@ -554,6 +629,33 @@ public class TrackBrowserActivity extends ListActivity
         }
     };
 
+    // Cursor should be positioned on the entry to be checked
+    // Returns false if the entry matches the naming pattern used for recordings,
+    // or if it is marked as not music in the database.
+    private boolean isMusic(Cursor c) {
+        int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
+        int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM);
+        int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST);
+
+        String title = c.getString(titleidx);
+        String album = c.getString(albumidx);
+        String artist = c.getString(artistidx);
+        if (MediaFile.UNKNOWN_STRING.equals(album) &&
+                MediaFile.UNKNOWN_STRING.equals(artist) &&
+                title != null &&
+                title.startsWith("recording")) {
+            // not music
+            return false;
+        }
+
+        int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC);
+        boolean ismusic = true;
+        if (ismusic_idx >= 0) {
+            ismusic = mTrackCursor.getInt(ismusic_idx) != 0;
+        }
+        return ismusic;
+    }
+
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
         menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
@@ -564,17 +666,20 @@ public class TrackBrowserActivity extends ListActivity
         }
         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);
+            mSelectedId = mTrackCursor.getLong(id_idx);
         } catch (IllegalArgumentException ex) {
             mSelectedId = mi.id;
         }
+        // only add the 'search' menu if the selected item is music
+        if (isMusic(mTrackCursor)) {
+            menu.add(0, SEARCH, 0, R.string.search_title);
+        }
         mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
                 MediaStore.Audio.Media.ALBUM));
         mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
@@ -595,7 +700,7 @@ public class TrackBrowserActivity extends ListActivity
             }
 
             case QUEUE: {
-                int [] list = new int[] { (int) mSelectedId };
+                long [] list = new long[] { mSelectedId };
                 MusicUtils.addToCurrentPlaylist(this, list);
                 return true;
             }
@@ -608,8 +713,8 @@ public class TrackBrowserActivity extends ListActivity
             }
 
             case PLAYLIST_SELECTED: {
-                int [] list = new int[] { (int) mSelectedId };
-                int playlist = item.getIntent().getIntExtra("playlist", 0);
+                long [] list = new long[] { mSelectedId };
+                long playlist = item.getIntent().getLongExtra("playlist", 0);
                 MusicUtils.addToPlaylist(this, list, playlist);
                 return true;
             }
@@ -620,13 +725,13 @@ public class TrackBrowserActivity extends ListActivity
                 return true;
 
             case DELETE_ITEM: {
-                int [] list = new int[1];
+                long [] list = new long[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);
+                b.putLongArray("items", list);
                 Intent intent = new Intent();
                 intent.setClass(this, DeleteItems.class);
                 intent.putExtras(b);
@@ -651,11 +756,18 @@ public class TrackBrowserActivity extends ListActivity
         
         Intent i = new Intent();
         i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         
-        title = mCurrentAlbumName;
-        query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
-        i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
-        i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
+        title = mCurrentTrackName;
+        if (MediaFile.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) {
+            query = mCurrentTrackName;
+        } else {
+            query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName;
+            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
+        }
+        if (MediaFile.UNKNOWN_STRING.equals(mCurrentAlbumName)) {
+            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);
@@ -781,6 +893,18 @@ public class TrackBrowserActivity extends ListActivity
         if (mTrackCursor.getCount() == 0) {
             return;
         }
+        // When selecting a track from the queue, just jump there instead of
+        // reloading the queue. This is both faster, and prevents accidentally
+        // dropping out of party shuffle.
+        if (mTrackCursor instanceof NowPlayingCursor) {
+            if (MusicUtils.sService != null) {
+                try {
+                    MusicUtils.sService.setQueuePosition(position);
+                    return;
+                } catch (RemoteException ex) {
+                }
+            }
+        }
         MusicUtils.playAll(this, mTrackCursor, position);
     }
 
@@ -795,9 +919,7 @@ public class TrackBrowserActivity extends ListActivity
         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, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
         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);
@@ -809,6 +931,12 @@ public class TrackBrowserActivity extends ListActivity
     }
 
     @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MusicUtils.setPartyShuffleMenuIcon(menu);
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         Intent intent;
         Cursor cursor;
@@ -818,18 +946,9 @@ public class TrackBrowserActivity extends ListActivity
                 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 PARTY_SHUFFLE:
+                MusicUtils.togglePartyShuffle();
+                break;
                 
             case SHUFFLE_ALL:
                 // Should 'shuffle all' shuffle ALL, or only the tracks shown?
@@ -864,7 +983,7 @@ public class TrackBrowserActivity extends ListActivity
                 if (resultCode == RESULT_CANCELED) {
                     finish();
                 } else {
-                    getTrackCursor(mAdapter.getQueryHandler(), null);
+                    getTrackCursor(mAdapter.getQueryHandler(), null, true);
                 }
                 break;
                 
@@ -872,7 +991,7 @@ public class TrackBrowserActivity extends ListActivity
                 if (resultCode == RESULT_OK) {
                     Uri uri = intent.getData();
                     if (uri != null) {
-                        int [] list = new int[] { (int) mSelectedId };
+                        long [] list = new long[] { mSelectedId };
                         MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
                     }
                 }
@@ -882,7 +1001,7 @@ public class TrackBrowserActivity extends ListActivity
                 if (resultCode == RESULT_OK) {
                     Uri uri = intent.getData();
                     if (uri != null) {
-                        int [] list = MusicUtils.getSongListForCursor(mTrackCursor);
+                        long [] list = MusicUtils.getSongListForCursor(mTrackCursor);
                         int plid = Integer.parseInt(uri.getLastPathSegment());
                         MusicUtils.addToPlaylist(this, list, plid);
                     }
@@ -891,12 +1010,18 @@ public class TrackBrowserActivity extends ListActivity
         }
     }
     
-    private Cursor getTrackCursor(AsyncQueryHandler async, String filter) {
+    private Cursor getTrackCursor(TrackListAdapter.TrackQueryHandler queryhandler, String filter,
+            boolean async) {
+
+        if (queryhandler == null) {
+            throw new IllegalArgumentException();
+        }
+
         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) {
@@ -910,24 +1035,15 @@ public class TrackBrowserActivity extends ListActivity
             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);
-            }
+            ret = queryhandler.doQuery(MediaStore.Audio.Genres.Members.getContentUri("external",
+                    Integer.valueOf(mGenre)),
+                    mCursorCols, where.toString(), keywords, mSortOrder, async);
         } else if (mPlaylist != null) {
             if (mPlaylist.equals("nowplaying")) {
                 if (MusicUtils.sService != null) {
@@ -940,43 +1056,22 @@ public class TrackBrowserActivity extends ListActivity
                 }
             } 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);
-                }
+                ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString(), keywords,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
             } 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);
-                }
+                ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        mCursorCols, where.toString(), keywords,
+                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
             } 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);
-                }
+                ret = queryhandler.doQuery(MediaStore.Audio.Playlists.Members.getContentUri("external",
+                        Long.valueOf(mPlaylist)), mPlaylistMemberCols,
+                        where.toString(), keywords, mSortOrder, async);
             }
         } else {
             if (mAlbumId != null) {
@@ -987,21 +1082,14 @@ public class TrackBrowserActivity extends ListActivity
                 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);
-            }
+            ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                    mCursorCols, where.toString() , keywords, mSortOrder, async);
         }
         
         // 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);
+        if (ret != null && async) {
+            init(ret, false);
             setTitle();
         }
         return ret;
@@ -1020,7 +1108,7 @@ public class TrackBrowserActivity extends ListActivity
             try {
                 mNowPlaying = mService.getQueue();
             } catch (RemoteException ex) {
-                mNowPlaying = new int[0];
+                mNowPlaying = new long[0];
             }
             mSize = mNowPlaying.length;
             if (mSize == 0) {
@@ -1047,11 +1135,11 @@ public class TrackBrowserActivity extends ListActivity
             }
             
             int size = mCurrentPlaylistCursor.getCount();
-            mCursorIdxs = new int[size];
+            mCursorIdxs = new long[size];
             mCurrentPlaylistCursor.moveToFirst();
             int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
             for (int i = 0; i < size; i++) {
-                mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx);
+                mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx);
                 mCurrentPlaylistCursor.moveToNext();
             }
             mCurrentPlaylistCursor.moveToFirst();
@@ -1064,7 +1152,7 @@ public class TrackBrowserActivity extends ListActivity
             try {
                 int removed = 0;
                 for (int i = mNowPlaying.length - 1; i >= 0; i--) {
-                    int trackid = mNowPlaying[i];
+                    long trackid = mNowPlaying[i];
                     int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
                     if (crsridx < 0) {
                         //Log.i("@@@@@", "item no longer exists in db: " + trackid);
@@ -1080,7 +1168,7 @@ public class TrackBrowserActivity extends ListActivity
                     }
                 }
             } catch (RemoteException ex) {
-                mNowPlaying = new int[0];
+                mNowPlaying = new long[0];
             }
         }
 
@@ -1104,7 +1192,7 @@ public class TrackBrowserActivity extends ListActivity
             // in queue-order, so we need to figure out where in the cursor we
             // should be.
            
-            int newid = mNowPlaying[newPosition];
+            long newid = mNowPlaying[newPosition];
             int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
             mCurrentPlaylistCursor.moveToPosition(crsridx);
             mCurPos = newPosition;
@@ -1231,8 +1319,8 @@ public class TrackBrowserActivity extends ListActivity
         private String [] mCols;
         private Cursor mCurrentPlaylistCursor;     // updated in onMove
         private int mSize;          // size of the queue
-        private int[] mNowPlaying;
-        private int[] mCursorIdxs;
+        private long[] mNowPlaying;
+        private long[] mCursorIdxs;
         private int mCurPos;
         private IMediaPlaybackService mService;
     }
@@ -1243,7 +1331,6 @@ public class TrackBrowserActivity extends ListActivity
 
         int mTitleIdx;
         int mArtistIdx;
-        int mAlbumIdx;
         int mDurationIdx;
         int mAudioIdIdx;
 
@@ -1254,9 +1341,11 @@ public class TrackBrowserActivity extends ListActivity
         private AlphabetIndexer mIndexer;
         
         private TrackBrowserActivity mActivity = null;
-        private AsyncQueryHandler mQueryHandler;
+        private TrackQueryHandler mQueryHandler;
+        private String mConstraint = null;
+        private boolean mConstraintIsValid = false;
         
-        class ViewHolder {
+        static class ViewHolder {
             TextView line1;
             TextView line2;
             TextView duration;
@@ -1265,15 +1354,50 @@ public class TrackBrowserActivity extends ListActivity
             char [] buffer2;
         }
 
-        class QueryHandler extends AsyncQueryHandler {
-            QueryHandler(ContentResolver res) {
+        class TrackQueryHandler extends AsyncQueryHandler {
+
+            class QueryArgs {
+                public Uri uri;
+                public String [] projection;
+                public String selection;
+                public String [] selectionArgs;
+                public String orderBy;
+            }
+
+            TrackQueryHandler(ContentResolver res) {
                 super(res);
             }
             
+            public Cursor doQuery(Uri uri, String[] projection,
+                    String selection, String[] selectionArgs,
+                    String orderBy, boolean async) {
+                if (async) {
+                    // Get 100 results first, which is enough to allow the user to start scrolling,
+                    // while still being very fast.
+                    Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build();
+                    QueryArgs args = new QueryArgs();
+                    args.uri = uri;
+                    args.projection = projection;
+                    args.selection = selection;
+                    args.selectionArgs = selectionArgs;
+                    args.orderBy = orderBy;
+
+                    startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy);
+                    return null;
+                }
+                return MusicUtils.query(mActivity,
+                        uri, projection, selection, selectionArgs, orderBy);
+            }
+
             @Override
             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                 //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
-                mActivity.init(cursor);
+                mActivity.init(cursor, cookie != null);
+                if (token == 0 && cookie != null && cursor != null && cursor.getCount() >= 100) {
+                    QueryArgs args = (QueryArgs) cookie;
+                    startQuery(1, null, args.uri, args.projection, args.selection,
+                            args.selectionArgs, args.orderBy);
+                }
             }
         }
         
@@ -1288,14 +1412,14 @@ public class TrackBrowserActivity extends ListActivity
             mUnknownArtist = context.getString(R.string.unknown_artist_name);
             mUnknownAlbum = context.getString(R.string.unknown_album_name);
             
-            mQueryHandler = new QueryHandler(context.getContentResolver());
+            mQueryHandler = new TrackQueryHandler(context.getContentResolver());
         }
         
         public void setActivity(TrackBrowserActivity newactivity) {
             mActivity = newactivity;
         }
         
-        public AsyncQueryHandler getQueryHandler() {
+        public TrackQueryHandler getQueryHandler() {
             return mQueryHandler;
         }
         
@@ -1303,7 +1427,6 @@ public class TrackBrowserActivity extends ListActivity
             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(
@@ -1377,7 +1500,7 @@ public class TrackBrowserActivity extends ListActivity
             vh.line2.setText(vh.buffer2, 0, len);
 
             ImageView iv = vh.play_indicator;
-            int id = -1;
+            long id = -1;
             if (MusicUtils.sService != null) {
                 // TODO: IPC call on each bind??
                 try {
@@ -1402,7 +1525,7 @@ public class TrackBrowserActivity extends ListActivity
             // 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)) {
+                 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getLong(mAudioIdIdx) == id)) {
                 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
                 iv.setVisibility(View.VISIBLE);
             } else {
@@ -1421,7 +1544,16 @@ public class TrackBrowserActivity extends ListActivity
         
         @Override
         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-            return mActivity.getTrackCursor(null, constraint.toString());
+            String s = constraint.toString();
+            if (mConstraintIsValid && (
+                    (s == null && mConstraint == null) ||
+                    (s != null && s.equals(mConstraint)))) {
+                return getCursor();
+            }
+            Cursor c = mActivity.getTrackCursor(mQueryHandler, s, false);
+            mConstraint = s;
+            mConstraintIsValid = true;
+            return c;
         }
         
         // SectionIndexer methods