2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.music;
19 import android.app.ListActivity;
20 import android.app.SearchManager;
21 import android.content.AsyncQueryHandler;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.ServiceConnection;
31 import android.database.AbstractCursor;
32 import android.database.CharArrayBuffer;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.media.AudioManager;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.Message;
41 import android.os.RemoteException;
42 import android.provider.MediaStore;
43 import android.provider.MediaStore.Audio.Playlists;
44 import android.util.Log;
45 import android.view.ContextMenu;
46 import android.view.KeyEvent;
47 import android.view.Menu;
48 import android.view.MenuItem;
49 import android.view.SubMenu;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.Window;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.widget.AlphabetIndexer;
55 import android.widget.ImageView;
56 import android.widget.ListView;
57 import android.widget.SectionIndexer;
58 import android.widget.SimpleCursorAdapter;
59 import android.widget.TextView;
60 import android.widget.AdapterView.AdapterContextMenuInfo;
62 import java.text.Collator;
63 import java.util.Arrays;
65 public class TrackBrowserActivity extends ListActivity
66 implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
68 private static final int Q_SELECTED = CHILD_MENU_BASE;
69 private static final int Q_ALL = CHILD_MENU_BASE + 1;
70 private static final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
71 private static final int PLAY_ALL = CHILD_MENU_BASE + 3;
72 private static final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
73 private static final int REMOVE = CHILD_MENU_BASE + 5;
74 private static final int SEARCH = CHILD_MENU_BASE + 6;
77 private static final String LOGTAG = "TrackBrowser";
79 private String[] mCursorCols;
80 private String[] mPlaylistMemberCols;
81 private boolean mDeletedOneRow = false;
82 private boolean mEditMode = false;
83 private String mCurrentTrackName;
84 private String mCurrentAlbumName;
85 private String mCurrentArtistNameForAlbum;
86 private ListView mTrackList;
87 private Cursor mTrackCursor;
88 private TrackListAdapter mAdapter;
89 private boolean mAdapterSent = false;
90 private String mAlbumId;
91 private String mArtistId;
92 private String mPlaylist;
93 private String mGenre;
94 private String mSortOrder;
95 private int mSelectedPosition;
96 private long mSelectedId;
97 private static int mLastListPosCourse = -1;
98 private static int mLastListPosFine = -1;
99 private boolean mUseLastListPos = false;
101 public TrackBrowserActivity()
105 /** Called when the activity is first created. */
107 public void onCreate(Bundle icicle)
109 super.onCreate(icicle);
110 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
111 Intent intent = getIntent();
112 if (intent != null) {
113 if (intent.getBooleanExtra("withtabs", false)) {
114 requestWindowFeature(Window.FEATURE_NO_TITLE);
117 setVolumeControlStream(AudioManager.STREAM_MUSIC);
118 if (icicle != null) {
119 mSelectedId = icicle.getLong("selectedtrack");
120 mAlbumId = icicle.getString("album");
121 mArtistId = icicle.getString("artist");
122 mPlaylist = icicle.getString("playlist");
123 mGenre = icicle.getString("genre");
124 mEditMode = icicle.getBoolean("editmode", false);
126 mAlbumId = intent.getStringExtra("album");
127 // If we have an album, show everything on the album, not just stuff
128 // by a particular artist.
129 mArtistId = intent.getStringExtra("artist");
130 mPlaylist = intent.getStringExtra("playlist");
131 mGenre = intent.getStringExtra("genre");
132 mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
135 mCursorCols = new String[] {
136 MediaStore.Audio.Media._ID,
137 MediaStore.Audio.Media.TITLE,
138 MediaStore.Audio.Media.DATA,
139 MediaStore.Audio.Media.ALBUM,
140 MediaStore.Audio.Media.ARTIST,
141 MediaStore.Audio.Media.ARTIST_ID,
142 MediaStore.Audio.Media.DURATION
144 mPlaylistMemberCols = new String[] {
145 MediaStore.Audio.Playlists.Members._ID,
146 MediaStore.Audio.Media.TITLE,
147 MediaStore.Audio.Media.DATA,
148 MediaStore.Audio.Media.ALBUM,
149 MediaStore.Audio.Media.ARTIST,
150 MediaStore.Audio.Media.ARTIST_ID,
151 MediaStore.Audio.Media.DURATION,
152 MediaStore.Audio.Playlists.Members.PLAY_ORDER,
153 MediaStore.Audio.Playlists.Members.AUDIO_ID,
154 MediaStore.Audio.Media.IS_MUSIC
157 setContentView(R.layout.media_picker_activity);
158 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
159 mTrackList = getListView();
160 mTrackList.setOnCreateContextMenuListener(this);
162 ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
163 ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
164 mTrackList.setCacheColorHint(0);
166 mTrackList.setTextFilterEnabled(true);
168 mAdapter = (TrackListAdapter) getLastNonConfigurationInstance();
170 if (mAdapter != null) {
171 mAdapter.setActivity(this);
172 setListAdapter(mAdapter);
174 MusicUtils.bindToService(this, this);
176 // don't set the album art until after the view has been layed out
177 mTrackList.post(new Runnable() {
180 setAlbumArtBackground();
185 public void onServiceConnected(ComponentName name, IBinder service)
187 IntentFilter f = new IntentFilter();
188 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
189 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
190 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
191 f.addDataScheme("file");
192 registerReceiver(mScanListener, f);
194 if (mAdapter == null) {
195 //Log.i("@@@", "starting query");
196 mAdapter = new TrackListAdapter(
197 getApplication(), // need to use application context to avoid leaks
199 mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
203 "nowplaying".equals(mPlaylist),
205 !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded")));
206 setListAdapter(mAdapter);
207 setTitle(R.string.working_songs);
208 getTrackCursor(mAdapter.getQueryHandler(), null, true);
210 mTrackCursor = mAdapter.getCursor();
211 // If mTrackCursor is null, this can be because it doesn't have
212 // a cursor yet (because the initial query that sets its cursor
213 // is still in progress), or because the query failed.
214 // In order to not flash the error dialog at the user for the
215 // first case, simply retry the query when the cursor is null.
216 // Worst case, we end up doing the same query twice.
217 if (mTrackCursor != null) {
218 init(mTrackCursor, false);
220 setTitle(R.string.working_songs);
221 getTrackCursor(mAdapter.getQueryHandler(), null, true);
225 MusicUtils.updateNowPlaying(this);
229 public void onServiceDisconnected(ComponentName name) {
230 // we can't really function without the service, so don't
235 public Object onRetainNonConfigurationInstance() {
236 TrackListAdapter a = mAdapter;
242 public void onDestroy() {
243 ListView lv = getListView();
244 if (lv != null && mUseLastListPos) {
245 mLastListPosCourse = lv.getFirstVisiblePosition();
246 View cv = lv.getChildAt(0);
248 mLastListPosFine = cv.getTop();
251 MusicUtils.unbindFromService(this);
253 if ("nowplaying".equals(mPlaylist)) {
254 unregisterReceiverSafe(mNowPlayingListener);
256 unregisterReceiverSafe(mTrackListListener);
258 } catch (IllegalArgumentException ex) {
259 // we end up here in case we never registered the listeners
262 // If we have an adapter and didn't send it off to another activity yet, we should
263 // close its cursor, which we do by assigning a null cursor to it. Doing this
264 // instead of closing the cursor directly keeps the framework from accessing
265 // the closed cursor later.
266 if (!mAdapterSent && mAdapter != null) {
267 mAdapter.changeCursor(null);
269 // Because we pass the adapter to the next activity, we need to make
270 // sure it doesn't keep a reference to this activity. We can do this
271 // by clearing its DatasetObservers, which setListAdapter(null) does.
272 setListAdapter(null);
274 unregisterReceiverSafe(mScanListener);
279 * Unregister a receiver, but eat the exception that is thrown if the
280 * receiver was never registered to begin with. This is a little easier
281 * than keeping track of whether the receivers have actually been
282 * registered by the time onDestroy() is called.
284 private void unregisterReceiverSafe(BroadcastReceiver receiver) {
286 unregisterReceiver(receiver);
287 } catch (IllegalArgumentException e) {
293 public void onResume() {
295 if (mTrackCursor != null) {
296 getListView().invalidateViews();
298 MusicUtils.setSpinnerState(this);
301 public void onPause() {
302 mReScanHandler.removeCallbacksAndMessages(null);
307 * This listener gets called when the media scanner starts up or finishes, and
308 * when the sd card is unmounted.
310 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
312 public void onReceive(Context context, Intent intent) {
313 String action = intent.getAction();
314 if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) ||
315 Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
316 MusicUtils.setSpinnerState(TrackBrowserActivity.this);
318 mReScanHandler.sendEmptyMessage(0);
322 private Handler mReScanHandler = new Handler() {
324 public void handleMessage(Message msg) {
325 if (mAdapter != null) {
326 getTrackCursor(mAdapter.getQueryHandler(), null, true);
328 // if the query results in a null cursor, onQueryComplete() will
329 // call init(), which will post a delayed message to this handler
330 // in order to try again.
334 public void onSaveInstanceState(Bundle outcicle) {
335 // need to store the selected item so we don't lose it in case
336 // of an orientation switch. Otherwise we could lose it while
337 // in the middle of specifying a playlist to add the item to.
338 outcicle.putLong("selectedtrack", mSelectedId);
339 outcicle.putString("artist", mArtistId);
340 outcicle.putString("album", mAlbumId);
341 outcicle.putString("playlist", mPlaylist);
342 outcicle.putString("genre", mGenre);
343 outcicle.putBoolean("editmode", mEditMode);
344 super.onSaveInstanceState(outcicle);
347 public void init(Cursor newCursor, boolean isLimited) {
349 if (mAdapter == null) {
352 mAdapter.changeCursor(newCursor); // also sets mTrackCursor
354 if (mTrackCursor == null) {
355 MusicUtils.displayDatabaseError(this);
357 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
361 MusicUtils.hideDatabaseError(this);
362 mUseLastListPos = MusicUtils.updateButtonBar(this, R.id.songtab);
365 // Restore previous position
366 if (mLastListPosCourse >= 0 && mUseLastListPos) {
367 ListView lv = getListView();
368 // this hack is needed because otherwise the position doesn't change
369 // for the 2nd (non-limited) cursor
370 lv.setAdapter(lv.getAdapter());
371 lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
373 mLastListPosCourse = -1;
377 // When showing the queue, position the selection on the currently playing track
378 // Otherwise, position the selection on the first matching artist, if any
379 IntentFilter f = new IntentFilter();
380 f.addAction(MediaPlaybackService.META_CHANGED);
381 f.addAction(MediaPlaybackService.QUEUE_CHANGED);
382 if ("nowplaying".equals(mPlaylist)) {
384 int cur = MusicUtils.sService.getQueuePosition();
386 registerReceiver(mNowPlayingListener, new IntentFilter(f));
387 mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
388 } catch (RemoteException ex) {
391 String key = getIntent().getStringExtra("artist");
393 int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
394 mTrackCursor.moveToFirst();
395 while (! mTrackCursor.isAfterLast()) {
396 String artist = mTrackCursor.getString(keyidx);
397 if (artist.equals(key)) {
398 setSelection(mTrackCursor.getPosition());
401 mTrackCursor.moveToNext();
404 registerReceiver(mTrackListListener, new IntentFilter(f));
405 mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
409 private void setAlbumArtBackground() {
411 long albumid = Long.valueOf(mAlbumId);
412 Bitmap bm = MusicUtils.getArtwork(TrackBrowserActivity.this, -1, albumid, false);
414 MusicUtils.setBackground(mTrackList, bm);
415 mTrackList.setCacheColorHint(0);
418 } catch (Exception ex) {
420 mTrackList.setBackgroundResource(0);
421 mTrackList.setCacheColorHint(0xff000000);
424 private void setTitle() {
426 CharSequence fancyName = null;
427 if (mAlbumId != null) {
428 int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
429 if (numresults > 0) {
430 mTrackCursor.moveToFirst();
431 int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
432 fancyName = mTrackCursor.getString(idx);
433 // For compilation albums show only the album title,
434 // but for regular albums show "artist - album".
435 // To determine whether something is a compilation
436 // album, do a query for the artist + album of the
437 // first item, and see if it returns the same number
438 // of results as the album query.
439 String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId +
440 "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" +
441 mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
442 MediaStore.Audio.Media.ARTIST_ID));
443 Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
444 new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
445 if (cursor != null) {
446 if (cursor.getCount() != numresults) {
448 fancyName = mTrackCursor.getString(idx);
452 if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING)) {
453 fancyName = getString(R.string.unknown_album_name);
456 } else if (mPlaylist != null) {
457 if (mPlaylist.equals("nowplaying")) {
458 if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
459 fancyName = getText(R.string.partyshuffle_title);
461 fancyName = getText(R.string.nowplaying_title);
463 } else if (mPlaylist.equals("podcasts")){
464 fancyName = getText(R.string.podcasts_title);
465 } else if (mPlaylist.equals("recentlyadded")){
466 fancyName = getText(R.string.recentlyadded_title);
468 String [] cols = new String [] {
469 MediaStore.Audio.Playlists.NAME
471 Cursor cursor = MusicUtils.query(this,
472 ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
473 cols, null, null, null);
474 if (cursor != null) {
475 if (cursor.getCount() != 0) {
476 cursor.moveToFirst();
477 fancyName = cursor.getString(0);
482 } else if (mGenre != null) {
483 String [] cols = new String [] {
484 MediaStore.Audio.Genres.NAME
486 Cursor cursor = MusicUtils.query(this,
487 ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
488 cols, null, null, null);
489 if (cursor != null) {
490 if (cursor.getCount() != 0) {
491 cursor.moveToFirst();
492 fancyName = cursor.getString(0);
498 if (fancyName != null) {
501 setTitle(R.string.tracks_title);
505 private TouchInterceptor.DropListener mDropListener =
506 new TouchInterceptor.DropListener() {
507 public void drop(int from, int to) {
508 if (mTrackCursor instanceof NowPlayingCursor) {
509 // update the currently playing list
510 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
511 c.moveItem(from, to);
512 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
513 getListView().invalidateViews();
514 mDeletedOneRow = true;
516 // update a saved playlist
517 MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(),
518 Long.valueOf(mPlaylist), from, to);
523 private TouchInterceptor.RemoveListener mRemoveListener =
524 new TouchInterceptor.RemoveListener() {
525 public void remove(int which) {
526 removePlaylistItem(which);
530 private void removePlaylistItem(int which) {
531 View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
533 Log.d(LOGTAG, "No view when removing playlist item " + which);
537 if (MusicUtils.sService != null
538 && which != MusicUtils.sService.getQueuePosition()) {
539 mDeletedOneRow = true;
541 } catch (RemoteException e) {
542 // Service died, so nothing playing.
543 mDeletedOneRow = true;
545 v.setVisibility(View.GONE);
546 mTrackList.invalidateViews();
547 if (mTrackCursor instanceof NowPlayingCursor) {
548 ((NowPlayingCursor)mTrackCursor).removeItem(which);
550 int colidx = mTrackCursor.getColumnIndexOrThrow(
551 MediaStore.Audio.Playlists.Members._ID);
552 mTrackCursor.moveToPosition(which);
553 long id = mTrackCursor.getLong(colidx);
554 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
555 Long.valueOf(mPlaylist));
556 getContentResolver().delete(
557 ContentUris.withAppendedId(uri, id), null, null);
559 v.setVisibility(View.VISIBLE);
560 mTrackList.invalidateViews();
563 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
565 public void onReceive(Context context, Intent intent) {
566 getListView().invalidateViews();
568 MusicUtils.updateNowPlaying(TrackBrowserActivity.this);
573 private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
575 public void onReceive(Context context, Intent intent) {
576 if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
577 getListView().invalidateViews();
578 } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
579 if (mDeletedOneRow) {
580 // This is the notification for a single row that was
581 // deleted previously, which is already reflected in
583 mDeletedOneRow = false;
586 Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
587 if (c.getCount() == 0) {
591 mAdapter.changeCursor(c);
596 // Cursor should be positioned on the entry to be checked
597 // Returns false if the entry matches the naming pattern used for recordings,
598 // or if it is marked as not music in the database.
599 private boolean isMusic(Cursor c) {
600 int titleidx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
601 int albumidx = c.getColumnIndex(MediaStore.Audio.Media.ALBUM);
602 int artistidx = c.getColumnIndex(MediaStore.Audio.Media.ARTIST);
604 String title = c.getString(titleidx);
605 String album = c.getString(albumidx);
606 String artist = c.getString(artistidx);
607 if (MediaStore.UNKNOWN_STRING.equals(album) &&
608 MediaStore.UNKNOWN_STRING.equals(artist) &&
610 title.startsWith("recording")) {
615 int ismusic_idx = c.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC);
616 boolean ismusic = true;
617 if (ismusic_idx >= 0) {
618 ismusic = mTrackCursor.getInt(ismusic_idx) != 0;
624 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
625 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
626 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
627 MusicUtils.makePlaylistMenu(this, sub);
629 menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
631 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
632 menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
633 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
634 mSelectedPosition = mi.position;
635 mTrackCursor.moveToPosition(mSelectedPosition);
637 int id_idx = mTrackCursor.getColumnIndexOrThrow(
638 MediaStore.Audio.Playlists.Members.AUDIO_ID);
639 mSelectedId = mTrackCursor.getLong(id_idx);
640 } catch (IllegalArgumentException ex) {
643 // only add the 'search' menu if the selected item is music
644 if (isMusic(mTrackCursor)) {
645 menu.add(0, SEARCH, 0, R.string.search_title);
647 mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
648 MediaStore.Audio.Media.ALBUM));
649 mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
650 MediaStore.Audio.Media.ARTIST));
651 mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
652 MediaStore.Audio.Media.TITLE));
653 menu.setHeaderTitle(mCurrentTrackName);
657 public boolean onContextItemSelected(MenuItem item) {
658 switch (item.getItemId()) {
659 case PLAY_SELECTION: {
661 int position = mSelectedPosition;
662 MusicUtils.playAll(this, mTrackCursor, position);
667 long [] list = new long[] { mSelectedId };
668 MusicUtils.addToCurrentPlaylist(this, list);
673 Intent intent = new Intent();
674 intent.setClass(this, CreatePlaylist.class);
675 startActivityForResult(intent, NEW_PLAYLIST);
679 case PLAYLIST_SELECTED: {
680 long [] list = new long[] { mSelectedId };
681 long playlist = item.getIntent().getLongExtra("playlist", 0);
682 MusicUtils.addToPlaylist(this, list, playlist);
686 case USE_AS_RINGTONE:
687 // Set the system setting to make this the current ringtone
688 MusicUtils.setRingtone(this, mSelectedId);
692 long [] list = new long[1];
693 list[0] = (int) mSelectedId;
694 Bundle b = new Bundle();
695 String f = getString(R.string.delete_song_desc);
696 String desc = String.format(f, mCurrentTrackName);
697 b.putString("description", desc);
698 b.putLongArray("items", list);
699 Intent intent = new Intent();
700 intent.setClass(this, DeleteItems.class);
702 startActivityForResult(intent, -1);
707 removePlaylistItem(mSelectedPosition);
714 return super.onContextItemSelected(item);
718 CharSequence title = null;
721 Intent i = new Intent();
722 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
723 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
725 title = mCurrentTrackName;
726 if (MediaStore.UNKNOWN_STRING.equals(mCurrentArtistNameForAlbum)) {
727 query = mCurrentTrackName;
729 query = mCurrentArtistNameForAlbum + " " + mCurrentTrackName;
730 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
732 if (MediaStore.UNKNOWN_STRING.equals(mCurrentAlbumName)) {
733 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
735 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
736 title = getString(R.string.mediasearch, title);
737 i.putExtra(SearchManager.QUERY, query);
739 startActivity(Intent.createChooser(i, title));
742 // In order to use alt-up/down as a shortcut for moving the selected item
743 // in the list, we need to override dispatchKeyEvent, not onKeyDown.
744 // (onKeyDown never sees these events, since they are handled by the list)
746 public boolean dispatchKeyEvent(KeyEvent event) {
747 if (mPlaylist != null && event.getMetaState() != 0 &&
748 event.getAction() == KeyEvent.ACTION_DOWN) {
749 switch (event.getKeyCode()) {
750 case KeyEvent.KEYCODE_DPAD_UP:
753 case KeyEvent.KEYCODE_DPAD_DOWN:
756 case KeyEvent.KEYCODE_DEL:
762 return super.dispatchKeyEvent(event);
765 private void removeItem() {
766 int curcount = mTrackCursor.getCount();
767 int curpos = mTrackList.getSelectedItemPosition();
768 if (curcount == 0 || curpos < 0) {
772 if ("nowplaying".equals(mPlaylist)) {
773 // remove track from queue
775 // Work around bug 902971. To get quick visual feedback
776 // of the deletion of the item, hide the selected view.
778 if (curpos != MusicUtils.sService.getQueuePosition()) {
779 mDeletedOneRow = true;
781 } catch (RemoteException ex) {
783 View v = mTrackList.getSelectedView();
784 v.setVisibility(View.GONE);
785 mTrackList.invalidateViews();
786 ((NowPlayingCursor)mTrackCursor).removeItem(curpos);
787 v.setVisibility(View.VISIBLE);
788 mTrackList.invalidateViews();
790 // remove track from playlist
791 int colidx = mTrackCursor.getColumnIndexOrThrow(
792 MediaStore.Audio.Playlists.Members._ID);
793 mTrackCursor.moveToPosition(curpos);
794 long id = mTrackCursor.getLong(colidx);
795 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
796 Long.valueOf(mPlaylist));
797 getContentResolver().delete(
798 ContentUris.withAppendedId(uri, id), null, null);
803 mTrackList.setSelection(curpos < curcount ? curpos : curcount);
808 private void moveItem(boolean up) {
809 int curcount = mTrackCursor.getCount();
810 int curpos = mTrackList.getSelectedItemPosition();
811 if ( (up && curpos < 1) || (!up && curpos >= curcount - 1)) {
815 if (mTrackCursor instanceof NowPlayingCursor) {
816 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
817 c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
818 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
819 getListView().invalidateViews();
820 mDeletedOneRow = true;
822 mTrackList.setSelection(curpos - 1);
824 mTrackList.setSelection(curpos + 1);
827 int colidx = mTrackCursor.getColumnIndexOrThrow(
828 MediaStore.Audio.Playlists.Members.PLAY_ORDER);
829 mTrackCursor.moveToPosition(curpos);
830 int currentplayidx = mTrackCursor.getInt(colidx);
831 Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
832 Long.valueOf(mPlaylist));
833 ContentValues values = new ContentValues();
834 String where = MediaStore.Audio.Playlists.Members._ID + "=?";
835 String [] wherearg = new String[1];
836 ContentResolver res = getContentResolver();
838 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
839 wherearg[0] = mTrackCursor.getString(0);
840 res.update(baseUri, values, where, wherearg);
841 mTrackCursor.moveToPrevious();
843 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
844 wherearg[0] = mTrackCursor.getString(0);
845 res.update(baseUri, values, where, wherearg);
846 mTrackCursor.moveToNext();
848 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
849 wherearg[0] = mTrackCursor.getString(0);
850 res.update(baseUri, values, where, wherearg);
855 protected void onListItemClick(ListView l, View v, int position, long id)
857 if (mTrackCursor.getCount() == 0) {
860 // When selecting a track from the queue, just jump there instead of
861 // reloading the queue. This is both faster, and prevents accidentally
862 // dropping out of party shuffle.
863 if (mTrackCursor instanceof NowPlayingCursor) {
864 if (MusicUtils.sService != null) {
866 MusicUtils.sService.setQueuePosition(position);
868 } catch (RemoteException ex) {
872 MusicUtils.playAll(this, mTrackCursor, position);
876 public boolean onCreateOptionsMenu(Menu menu) {
877 /* This activity is used for a number of different browsing modes, and the menu can
878 * be different for each of them:
879 * - all tracks, optionally restricted to an album, artist or playlist
880 * - the list of currently playing songs
882 super.onCreateOptionsMenu(menu);
883 if (mPlaylist == null) {
884 menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(R.drawable.ic_menu_play_clip);
886 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
887 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
888 if (mPlaylist != null) {
889 menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save);
890 if (mPlaylist.equals("nowplaying")) {
891 menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(R.drawable.ic_menu_clear_playlist);
898 public boolean onPrepareOptionsMenu(Menu menu) {
899 MusicUtils.setPartyShuffleMenuIcon(menu);
900 return super.onPrepareOptionsMenu(menu);
904 public boolean onOptionsItemSelected(MenuItem item) {
907 switch (item.getItemId()) {
909 MusicUtils.playAll(this, mTrackCursor);
914 MusicUtils.togglePartyShuffle();
918 // Should 'shuffle all' shuffle ALL, or only the tracks shown?
919 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
920 new String [] { MediaStore.Audio.Media._ID},
921 MediaStore.Audio.Media.IS_MUSIC + "=1", null,
922 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
923 if (cursor != null) {
924 MusicUtils.shuffleAll(this, cursor);
929 case SAVE_AS_PLAYLIST:
930 intent = new Intent();
931 intent.setClass(this, CreatePlaylist.class);
932 startActivityForResult(intent, SAVE_AS_PLAYLIST);
936 // We only clear the current playlist
937 MusicUtils.clearQueue();
940 return super.onOptionsItemSelected(item);
944 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
945 switch (requestCode) {
947 if (resultCode == RESULT_CANCELED) {
950 getTrackCursor(mAdapter.getQueryHandler(), null, true);
955 if (resultCode == RESULT_OK) {
956 Uri uri = intent.getData();
958 long [] list = new long[] { mSelectedId };
959 MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
964 case SAVE_AS_PLAYLIST:
965 if (resultCode == RESULT_OK) {
966 Uri uri = intent.getData();
968 long [] list = MusicUtils.getSongListForCursor(mTrackCursor);
969 int plid = Integer.parseInt(uri.getLastPathSegment());
970 MusicUtils.addToPlaylist(this, list, plid);
977 private Cursor getTrackCursor(TrackListAdapter.TrackQueryHandler queryhandler, String filter,
980 if (queryhandler == null) {
981 throw new IllegalArgumentException();
985 mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
986 StringBuilder where = new StringBuilder();
987 where.append(MediaStore.Audio.Media.TITLE + " != ''");
989 // Add in the filtering constraints
990 String [] keywords = null;
991 if (filter != null) {
992 String [] searchWords = filter.split(" ");
993 keywords = new String[searchWords.length];
994 Collator col = Collator.getInstance();
995 col.setStrength(Collator.PRIMARY);
996 for (int i = 0; i < searchWords.length; i++) {
997 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
999 for (int i = 0; i < searchWords.length; i++) {
1000 where.append(" AND ");
1001 where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
1002 where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
1006 if (mGenre != null) {
1007 mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
1008 ret = queryhandler.doQuery(MediaStore.Audio.Genres.Members.getContentUri("external",
1009 Integer.valueOf(mGenre)),
1010 mCursorCols, where.toString(), keywords, mSortOrder, async);
1011 } else if (mPlaylist != null) {
1012 if (mPlaylist.equals("nowplaying")) {
1013 if (MusicUtils.sService != null) {
1014 ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
1015 if (ret.getCount() == 0) {
1019 // Nothing is playing.
1021 } else if (mPlaylist.equals("podcasts")) {
1022 where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1");
1023 ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1024 mCursorCols, where.toString(), keywords,
1025 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
1026 } else if (mPlaylist.equals("recentlyadded")) {
1027 // do a query for all songs added in the last X weeks
1028 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
1029 where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">");
1030 where.append(System.currentTimeMillis() / 1000 - X);
1031 ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1032 mCursorCols, where.toString(), keywords,
1033 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, async);
1035 mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
1036 ret = queryhandler.doQuery(MediaStore.Audio.Playlists.Members.getContentUri("external",
1037 Long.valueOf(mPlaylist)), mPlaylistMemberCols,
1038 where.toString(), keywords, mSortOrder, async);
1041 if (mAlbumId != null) {
1042 where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
1043 mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
1045 if (mArtistId != null) {
1046 where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId);
1048 where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
1049 ret = queryhandler.doQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1050 mCursorCols, where.toString() , keywords, mSortOrder, async);
1053 // This special case is for the "nowplaying" cursor, which cannot be handled
1054 // asynchronously using AsyncQueryHandler, so we do some extra initialization here.
1055 if (ret != null && async) {
1062 private class NowPlayingCursor extends AbstractCursor
1064 public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
1068 makeNowPlayingCursor();
1070 private void makeNowPlayingCursor() {
1071 mCurrentPlaylistCursor = null;
1073 mNowPlaying = mService.getQueue();
1074 } catch (RemoteException ex) {
1075 mNowPlaying = new long[0];
1077 mSize = mNowPlaying.length;
1082 StringBuilder where = new StringBuilder();
1083 where.append(MediaStore.Audio.Media._ID + " IN (");
1084 for (int i = 0; i < mSize; i++) {
1085 where.append(mNowPlaying[i]);
1086 if (i < mSize - 1) {
1092 mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
1093 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1094 mCols, where.toString(), null, MediaStore.Audio.Media._ID);
1096 if (mCurrentPlaylistCursor == null) {
1101 int size = mCurrentPlaylistCursor.getCount();
1102 mCursorIdxs = new long[size];
1103 mCurrentPlaylistCursor.moveToFirst();
1104 int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1105 for (int i = 0; i < size; i++) {
1106 mCursorIdxs[i] = mCurrentPlaylistCursor.getLong(colidx);
1107 mCurrentPlaylistCursor.moveToNext();
1109 mCurrentPlaylistCursor.moveToFirst();
1112 // At this point we can verify the 'now playing' list we got
1113 // earlier to make sure that all the items in there still exist
1114 // in the database, and remove those that aren't. This way we
1115 // don't get any blank items in the list.
1118 for (int i = mNowPlaying.length - 1; i >= 0; i--) {
1119 long trackid = mNowPlaying[i];
1120 int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
1122 //Log.i("@@@@@", "item no longer exists in db: " + trackid);
1123 removed += mService.removeTrack(trackid);
1127 mNowPlaying = mService.getQueue();
1128 mSize = mNowPlaying.length;
1134 } catch (RemoteException ex) {
1135 mNowPlaying = new long[0];
1140 public int getCount()
1146 public boolean onMove(int oldPosition, int newPosition)
1148 if (oldPosition == newPosition)
1151 if (mNowPlaying == null || mCursorIdxs == null) {
1155 // The cursor doesn't have any duplicates in it, and is not ordered
1156 // in queue-order, so we need to figure out where in the cursor we
1159 long newid = mNowPlaying[newPosition];
1160 int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
1161 mCurrentPlaylistCursor.moveToPosition(crsridx);
1162 mCurPos = newPosition;
1167 public boolean removeItem(int which)
1170 if (mService.removeTracks(which, which) == 0) {
1171 return false; // delete failed
1173 int i = (int) which;
1176 mNowPlaying[i] = mNowPlaying[i+1];
1179 onMove(-1, (int) mCurPos);
1180 } catch (RemoteException ex) {
1185 public void moveItem(int from, int to) {
1187 mService.moveQueueItem(from, to);
1188 mNowPlaying = mService.getQueue();
1189 onMove(-1, mCurPos); // update the underlying cursor
1190 } catch (RemoteException ex) {
1194 private void dump() {
1196 for (int i = 0; i < mSize; i++) {
1197 where += mNowPlaying[i];
1198 if (i < mSize - 1) {
1203 Log.i("NowPlayingCursor: ", where);
1207 public String getString(int column)
1210 return mCurrentPlaylistCursor.getString(column);
1211 } catch (Exception ex) {
1218 public short getShort(int column)
1220 return mCurrentPlaylistCursor.getShort(column);
1224 public int getInt(int column)
1227 return mCurrentPlaylistCursor.getInt(column);
1228 } catch (Exception ex) {
1235 public long getLong(int column)
1238 return mCurrentPlaylistCursor.getLong(column);
1239 } catch (Exception ex) {
1246 public float getFloat(int column)
1248 return mCurrentPlaylistCursor.getFloat(column);
1252 public double getDouble(int column)
1254 return mCurrentPlaylistCursor.getDouble(column);
1258 public boolean isNull(int column)
1260 return mCurrentPlaylistCursor.isNull(column);
1264 public String[] getColumnNames()
1270 public void deactivate()
1272 if (mCurrentPlaylistCursor != null)
1273 mCurrentPlaylistCursor.deactivate();
1277 public boolean requery()
1279 makeNowPlayingCursor();
1283 private String [] mCols;
1284 private Cursor mCurrentPlaylistCursor; // updated in onMove
1285 private int mSize; // size of the queue
1286 private long[] mNowPlaying;
1287 private long[] mCursorIdxs;
1288 private int mCurPos;
1289 private IMediaPlaybackService mService;
1292 static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
1293 boolean mIsNowPlaying;
1294 boolean mDisableNowPlayingIndicator;
1301 private final StringBuilder mBuilder = new StringBuilder();
1302 private final String mUnknownArtist;
1303 private final String mUnknownAlbum;
1305 private AlphabetIndexer mIndexer;
1307 private TrackBrowserActivity mActivity = null;
1308 private TrackQueryHandler mQueryHandler;
1309 private String mConstraint = null;
1310 private boolean mConstraintIsValid = false;
1312 static class ViewHolder {
1316 ImageView play_indicator;
1317 CharArrayBuffer buffer1;
1321 class TrackQueryHandler extends AsyncQueryHandler {
1325 public String [] projection;
1326 public String selection;
1327 public String [] selectionArgs;
1328 public String orderBy;
1331 TrackQueryHandler(ContentResolver res) {
1335 public Cursor doQuery(Uri uri, String[] projection,
1336 String selection, String[] selectionArgs,
1337 String orderBy, boolean async) {
1339 // Get 100 results first, which is enough to allow the user to start scrolling,
1340 // while still being very fast.
1341 Uri limituri = uri.buildUpon().appendQueryParameter("limit", "100").build();
1342 QueryArgs args = new QueryArgs();
1344 args.projection = projection;
1345 args.selection = selection;
1346 args.selectionArgs = selectionArgs;
1347 args.orderBy = orderBy;
1349 startQuery(0, args, limituri, projection, selection, selectionArgs, orderBy);
1352 return MusicUtils.query(mActivity,
1353 uri, projection, selection, selectionArgs, orderBy);
1357 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
1358 //Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity);
1359 mActivity.init(cursor, cookie != null);
1360 if (token == 0 && cookie != null && cursor != null && cursor.getCount() >= 100) {
1361 QueryArgs args = (QueryArgs) cookie;
1362 startQuery(1, null, args.uri, args.projection, args.selection,
1363 args.selectionArgs, args.orderBy);
1368 TrackListAdapter(Context context, TrackBrowserActivity currentactivity,
1369 int layout, Cursor cursor, String[] from, int[] to,
1370 boolean isnowplaying, boolean disablenowplayingindicator) {
1371 super(context, layout, cursor, from, to);
1372 mActivity = currentactivity;
1373 getColumnIndices(cursor);
1374 mIsNowPlaying = isnowplaying;
1375 mDisableNowPlayingIndicator = disablenowplayingindicator;
1376 mUnknownArtist = context.getString(R.string.unknown_artist_name);
1377 mUnknownAlbum = context.getString(R.string.unknown_album_name);
1379 mQueryHandler = new TrackQueryHandler(context.getContentResolver());
1382 public void setActivity(TrackBrowserActivity newactivity) {
1383 mActivity = newactivity;
1386 public TrackQueryHandler getQueryHandler() {
1387 return mQueryHandler;
1390 private void getColumnIndices(Cursor cursor) {
1391 if (cursor != null) {
1392 mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
1393 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
1394 mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
1396 mAudioIdIdx = cursor.getColumnIndexOrThrow(
1397 MediaStore.Audio.Playlists.Members.AUDIO_ID);
1398 } catch (IllegalArgumentException ex) {
1399 mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1402 if (mIndexer != null) {
1403 mIndexer.setCursor(cursor);
1404 } else if (!mActivity.mEditMode) {
1405 String alpha = mActivity.getString(R.string.fast_scroll_alphabet);
1407 mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha);
1413 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1414 View v = super.newView(context, cursor, parent);
1415 ImageView iv = (ImageView) v.findViewById(R.id.icon);
1416 if (mActivity.mEditMode) {
1417 iv.setVisibility(View.VISIBLE);
1418 iv.setImageResource(R.drawable.ic_mp_move);
1420 iv.setVisibility(View.GONE);
1423 ViewHolder vh = new ViewHolder();
1424 vh.line1 = (TextView) v.findViewById(R.id.line1);
1425 vh.line2 = (TextView) v.findViewById(R.id.line2);
1426 vh.duration = (TextView) v.findViewById(R.id.duration);
1427 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
1428 vh.buffer1 = new CharArrayBuffer(100);
1429 vh.buffer2 = new char[200];
1435 public void bindView(View view, Context context, Cursor cursor) {
1437 ViewHolder vh = (ViewHolder) view.getTag();
1439 cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
1440 vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
1442 int secs = cursor.getInt(mDurationIdx) / 1000;
1444 vh.duration.setText("");
1446 vh.duration.setText(MusicUtils.makeTimeString(context, secs));
1449 final StringBuilder builder = mBuilder;
1450 builder.delete(0, builder.length());
1452 String name = cursor.getString(mArtistIdx);
1453 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
1454 builder.append(mUnknownArtist);
1456 builder.append(name);
1458 int len = builder.length();
1459 if (vh.buffer2.length < len) {
1460 vh.buffer2 = new char[len];
1462 builder.getChars(0, len, vh.buffer2, 0);
1463 vh.line2.setText(vh.buffer2, 0, len);
1465 ImageView iv = vh.play_indicator;
1467 if (MusicUtils.sService != null) {
1468 // TODO: IPC call on each bind??
1470 if (mIsNowPlaying) {
1471 id = MusicUtils.sService.getQueuePosition();
1473 id = MusicUtils.sService.getAudioId();
1475 } catch (RemoteException ex) {
1479 // Determining whether and where to show the "now playing indicator
1480 // is tricky, because we don't actually keep track of where the songs
1481 // in the current playlist came from after they've started playing.
1483 // If the "current playlists" is shown, then we can simply match by position,
1484 // otherwise, we need to match by id. Match-by-id gets a little weird if
1485 // a song appears in a playlist more than once, and you're in edit-playlist
1486 // mode. In that case, both items will have the "now playing" indicator.
1487 // For this reason, we don't show the play indicator at all when in edit
1488 // playlist mode (except when you're viewing the "current playlist",
1489 // which is not really a playlist)
1490 if ( (mIsNowPlaying && cursor.getPosition() == id) ||
1491 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getLong(mAudioIdIdx) == id)) {
1492 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
1493 iv.setVisibility(View.VISIBLE);
1495 iv.setVisibility(View.GONE);
1500 public void changeCursor(Cursor cursor) {
1501 if (mActivity.isFinishing() && cursor != null) {
1505 if (cursor != mActivity.mTrackCursor) {
1506 mActivity.mTrackCursor = cursor;
1507 super.changeCursor(cursor);
1508 getColumnIndices(cursor);
1513 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1514 String s = constraint.toString();
1515 if (mConstraintIsValid && (
1516 (s == null && mConstraint == null) ||
1517 (s != null && s.equals(mConstraint)))) {
1520 Cursor c = mActivity.getTrackCursor(mQueryHandler, s, false);
1522 mConstraintIsValid = true;
1526 // SectionIndexer methods
1528 public Object[] getSections() {
1529 if (mIndexer != null) {
1530 return mIndexer.getSections();
1536 public int getPositionForSection(int section) {
1537 int pos = mIndexer.getPositionForSection(section);
1541 public int getSectionForPosition(int position) {