TrackBrowserActivity.java revision 4341b5006cffbbd016688d4533f6de68d9d4c49b
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.music; 18 19import android.app.ListActivity; 20import android.app.SearchManager; 21import android.content.AsyncQueryHandler; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.ContentResolver; 25import android.content.ContentUris; 26import android.content.ContentValues; 27import android.content.Context; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.content.ServiceConnection; 31import android.database.AbstractCursor; 32import android.database.CharArrayBuffer; 33import android.database.Cursor; 34import android.media.AudioManager; 35import android.media.MediaFile; 36import android.net.Uri; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.IBinder; 40import android.os.Message; 41import android.os.RemoteException; 42import android.provider.MediaStore; 43import android.provider.MediaStore.Audio.Playlists; 44import android.util.Log; 45import android.view.ContextMenu; 46import android.view.KeyEvent; 47import android.view.Menu; 48import android.view.MenuItem; 49import android.view.SubMenu; 50import android.view.View; 51import android.view.ViewGroup; 52import android.view.Window; 53import android.view.ContextMenu.ContextMenuInfo; 54import android.widget.AlphabetIndexer; 55import android.widget.ImageView; 56import android.widget.ListView; 57import android.widget.SectionIndexer; 58import android.widget.SimpleCursorAdapter; 59import android.widget.TextView; 60import android.widget.AdapterView.AdapterContextMenuInfo; 61 62import java.text.Collator; 63import java.util.Arrays; 64 65public class TrackBrowserActivity extends ListActivity 66 implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection 67{ 68 private final int Q_SELECTED = CHILD_MENU_BASE; 69 private final int Q_ALL = CHILD_MENU_BASE + 1; 70 private final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2; 71 private final int PLAY_ALL = CHILD_MENU_BASE + 3; 72 private final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4; 73 private final int REMOVE = CHILD_MENU_BASE + 5; 74 private final int SEARCH = CHILD_MENU_BASE + 6; 75 76 77 private static final String LOGTAG = "TrackBrowser"; 78 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 98 public TrackBrowserActivity() 99 { 100 } 101 102 /** Called when the activity is first created. */ 103 @Override 104 public void onCreate(Bundle icicle) 105 { 106 super.onCreate(icicle); 107 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 108 setVolumeControlStream(AudioManager.STREAM_MUSIC); 109 if (icicle != null) { 110 mSelectedId = icicle.getLong("selectedtrack"); 111 mAlbumId = icicle.getString("album"); 112 mArtistId = icicle.getString("artist"); 113 mPlaylist = icicle.getString("playlist"); 114 mGenre = icicle.getString("genre"); 115 mEditMode = icicle.getBoolean("editmode", false); 116 } else { 117 mAlbumId = getIntent().getStringExtra("album"); 118 // If we have an album, show everything on the album, not just stuff 119 // by a particular artist. 120 Intent intent = getIntent(); 121 mArtistId = intent.getStringExtra("artist"); 122 mPlaylist = intent.getStringExtra("playlist"); 123 mGenre = intent.getStringExtra("genre"); 124 mEditMode = intent.getAction().equals(Intent.ACTION_EDIT); 125 } 126 127 mCursorCols = new String[] { 128 MediaStore.Audio.Media._ID, 129 MediaStore.Audio.Media.TITLE, 130 MediaStore.Audio.Media.TITLE_KEY, 131 MediaStore.Audio.Media.DATA, 132 MediaStore.Audio.Media.ALBUM, 133 MediaStore.Audio.Media.ARTIST, 134 MediaStore.Audio.Media.ARTIST_ID, 135 MediaStore.Audio.Media.DURATION 136 }; 137 mPlaylistMemberCols = new String[] { 138 MediaStore.Audio.Playlists.Members._ID, 139 MediaStore.Audio.Media.TITLE, 140 MediaStore.Audio.Media.TITLE_KEY, 141 MediaStore.Audio.Media.DATA, 142 MediaStore.Audio.Media.ALBUM, 143 MediaStore.Audio.Media.ARTIST, 144 MediaStore.Audio.Media.ARTIST_ID, 145 MediaStore.Audio.Media.DURATION, 146 MediaStore.Audio.Playlists.Members.PLAY_ORDER, 147 MediaStore.Audio.Playlists.Members.AUDIO_ID 148 }; 149 150 setContentView(R.layout.media_picker_activity); 151 mTrackList = getListView(); 152 mTrackList.setOnCreateContextMenuListener(this); 153 if (mEditMode) { 154 //((TouchInterceptor) mTrackList).setDragListener(mDragListener); 155 ((TouchInterceptor) mTrackList).setDropListener(mDropListener); 156 ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener); 157 mTrackList.setCacheColorHint(0); 158 } else { 159 mTrackList.setTextFilterEnabled(true); 160 } 161 mAdapter = (TrackListAdapter) getLastNonConfigurationInstance(); 162 163 if (mAdapter != null) { 164 mAdapter.setActivity(this); 165 setListAdapter(mAdapter); 166 } 167 MusicUtils.bindToService(this, this); 168 } 169 170 public void onServiceConnected(ComponentName name, IBinder service) 171 { 172 IntentFilter f = new IntentFilter(); 173 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 174 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 175 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 176 f.addDataScheme("file"); 177 registerReceiver(mScanListener, f); 178 179 if (mAdapter == null) { 180 //Log.i("@@@", "starting query"); 181 mAdapter = new TrackListAdapter( 182 getApplication(), // need to use application context to avoid leaks 183 this, 184 mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item, 185 null, // cursor 186 new String[] {}, 187 new int[] {}, 188 "nowplaying".equals(mPlaylist), 189 mPlaylist != null && 190 !(mPlaylist.equals("podcasts") || mPlaylist.equals("recentlyadded"))); 191 setListAdapter(mAdapter); 192 setTitle(R.string.working_songs); 193 getTrackCursor(mAdapter.getQueryHandler(), null); 194 } else { 195 mTrackCursor = mAdapter.getCursor(); 196 // If mTrackCursor is null, this can be because it doesn't have 197 // a cursor yet (because the initial query that sets its cursor 198 // is still in progress), or because the query failed. 199 // In order to not flash the error dialog at the user for the 200 // first case, simply retry the query when the cursor is null. 201 // Worst case, we end up doing the same query twice. 202 if (mTrackCursor != null) { 203 init(mTrackCursor); 204 } else { 205 setTitle(R.string.working_songs); 206 getTrackCursor(mAdapter.getQueryHandler(), null); 207 } 208 } 209 } 210 211 public void onServiceDisconnected(ComponentName name) { 212 // we can't really function without the service, so don't 213 finish(); 214 } 215 216 @Override 217 public Object onRetainNonConfigurationInstance() { 218 TrackListAdapter a = mAdapter; 219 mAdapterSent = true; 220 return a; 221 } 222 223 @Override 224 public void onDestroy() { 225 MusicUtils.unbindFromService(this); 226 try { 227 if ("nowplaying".equals(mPlaylist)) { 228 unregisterReceiverSafe(mNowPlayingListener); 229 } else { 230 unregisterReceiverSafe(mTrackListListener); 231 } 232 } catch (IllegalArgumentException ex) { 233 // we end up here in case we never registered the listeners 234 } 235 236 // if we didn't send the adapter off to another activity, we should 237 // close the cursor 238 if (!mAdapterSent) { 239 Cursor c = mAdapter.getCursor(); 240 if (c != null) { 241 c.close(); 242 } 243 } 244 unregisterReceiverSafe(mScanListener); 245 super.onDestroy(); 246 } 247 248 /** 249 * Unregister a receiver, but eat the exception that is thrown if the 250 * receiver was never registered to begin with. This is a little easier 251 * than keeping track of whether the receivers have actually been 252 * registered by the time onDestroy() is called. 253 */ 254 private void unregisterReceiverSafe(BroadcastReceiver receiver) { 255 try { 256 unregisterReceiver(receiver); 257 } catch (IllegalArgumentException e) { 258 // ignore 259 } 260 } 261 262 @Override 263 public void onResume() { 264 super.onResume(); 265 if (mTrackCursor != null) { 266 getListView().invalidateViews(); 267 } 268 MusicUtils.setSpinnerState(this); 269 } 270 @Override 271 public void onPause() { 272 mReScanHandler.removeCallbacksAndMessages(null); 273 super.onPause(); 274 } 275 276 /* 277 * This listener gets called when the media scanner starts up or finishes, and 278 * when the sd card is unmounted. 279 */ 280 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 281 @Override 282 public void onReceive(Context context, Intent intent) { 283 String action = intent.getAction(); 284 if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action) || 285 Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) { 286 MusicUtils.setSpinnerState(TrackBrowserActivity.this); 287 } 288 mReScanHandler.sendEmptyMessage(0); 289 } 290 }; 291 292 private Handler mReScanHandler = new Handler() { 293 @Override 294 public void handleMessage(Message msg) { 295 getTrackCursor(mAdapter.getQueryHandler(), null); 296 // if the query results in a null cursor, onQueryComplete() will 297 // call init(), which will post a delayed message to this handler 298 // in order to try again. 299 } 300 }; 301 302 public void onSaveInstanceState(Bundle outcicle) { 303 // need to store the selected item so we don't lose it in case 304 // of an orientation switch. Otherwise we could lose it while 305 // in the middle of specifying a playlist to add the item to. 306 outcicle.putLong("selectedtrack", mSelectedId); 307 outcicle.putString("artist", mArtistId); 308 outcicle.putString("album", mAlbumId); 309 outcicle.putString("playlist", mPlaylist); 310 outcicle.putString("genre", mGenre); 311 outcicle.putBoolean("editmode", mEditMode); 312 super.onSaveInstanceState(outcicle); 313 } 314 315 public void init(Cursor newCursor) { 316 317 mAdapter.changeCursor(newCursor); // also sets mTrackCursor 318 319 if (mTrackCursor == null) { 320 MusicUtils.displayDatabaseError(this); 321 closeContextMenu(); 322 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 323 return; 324 } 325 326 MusicUtils.hideDatabaseError(this); 327 setTitle(); 328 329 // When showing the queue, position the selection on the currently playing track 330 // Otherwise, position the selection on the first matching artist, if any 331 IntentFilter f = new IntentFilter(); 332 f.addAction(MediaPlaybackService.META_CHANGED); 333 f.addAction(MediaPlaybackService.QUEUE_CHANGED); 334 if ("nowplaying".equals(mPlaylist)) { 335 try { 336 int cur = MusicUtils.sService.getQueuePosition(); 337 setSelection(cur); 338 registerReceiver(mNowPlayingListener, new IntentFilter(f)); 339 mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED)); 340 } catch (RemoteException ex) { 341 } 342 } else { 343 String key = getIntent().getStringExtra("artist"); 344 if (key != null) { 345 int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID); 346 mTrackCursor.moveToFirst(); 347 while (! mTrackCursor.isAfterLast()) { 348 String artist = mTrackCursor.getString(keyidx); 349 if (artist.equals(key)) { 350 setSelection(mTrackCursor.getPosition()); 351 break; 352 } 353 mTrackCursor.moveToNext(); 354 } 355 } 356 registerReceiver(mTrackListListener, new IntentFilter(f)); 357 mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED)); 358 } 359 } 360 361 private void setTitle() { 362 363 CharSequence fancyName = null; 364 if (mAlbumId != null) { 365 int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0; 366 if (numresults > 0) { 367 mTrackCursor.moveToFirst(); 368 int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM); 369 fancyName = mTrackCursor.getString(idx); 370 // For compilation albums show only the album title, 371 // but for regular albums show "artist - album". 372 // To determine whether something is a compilation 373 // album, do a query for the artist + album of the 374 // first item, and see if it returns the same number 375 // of results as the album query. 376 String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId + 377 "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + 378 mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow( 379 MediaStore.Audio.Media.ARTIST_ID)); 380 Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 381 new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null); 382 if (cursor != null) { 383 if (cursor.getCount() != numresults) { 384 // compilation album 385 fancyName = mTrackCursor.getString(idx); 386 } 387 cursor.deactivate(); 388 } 389 if (fancyName == null || fancyName.equals(MediaFile.UNKNOWN_STRING)) { 390 fancyName = getString(R.string.unknown_album_name); 391 } 392 } 393 } else if (mPlaylist != null) { 394 if (mPlaylist.equals("nowplaying")) { 395 if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) { 396 fancyName = getText(R.string.partyshuffle_title); 397 } else { 398 fancyName = getText(R.string.nowplaying_title); 399 } 400 } else if (mPlaylist.equals("podcasts")){ 401 fancyName = getText(R.string.podcasts_title); 402 } else if (mPlaylist.equals("recentlyadded")){ 403 fancyName = getText(R.string.recentlyadded_title); 404 } else { 405 String [] cols = new String [] { 406 MediaStore.Audio.Playlists.NAME 407 }; 408 Cursor cursor = MusicUtils.query(this, 409 ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)), 410 cols, null, null, null); 411 if (cursor != null) { 412 if (cursor.getCount() != 0) { 413 cursor.moveToFirst(); 414 fancyName = cursor.getString(0); 415 } 416 cursor.deactivate(); 417 } 418 } 419 } else if (mGenre != null) { 420 String [] cols = new String [] { 421 MediaStore.Audio.Genres.NAME 422 }; 423 Cursor cursor = MusicUtils.query(this, 424 ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)), 425 cols, null, null, null); 426 if (cursor != null) { 427 if (cursor.getCount() != 0) { 428 cursor.moveToFirst(); 429 fancyName = cursor.getString(0); 430 } 431 cursor.deactivate(); 432 } 433 } 434 435 if (fancyName != null) { 436 setTitle(fancyName); 437 } else { 438 setTitle(R.string.tracks_title); 439 } 440 } 441 442 private TouchInterceptor.DragListener mDragListener = 443 new TouchInterceptor.DragListener() { 444 public void drag(int from, int to) { 445 if (mTrackCursor instanceof NowPlayingCursor) { 446 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor; 447 c.moveItem(from, to); 448 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged(); 449 getListView().invalidateViews(); 450 mDeletedOneRow = true; 451 } 452 } 453 }; 454 private TouchInterceptor.DropListener mDropListener = 455 new TouchInterceptor.DropListener() { 456 public void drop(int from, int to) { 457 if (mTrackCursor instanceof NowPlayingCursor) { 458 // update the currently playing list 459 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor; 460 c.moveItem(from, to); 461 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged(); 462 getListView().invalidateViews(); 463 mDeletedOneRow = true; 464 } else { 465 // update a saved playlist 466 Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external", 467 Long.valueOf(mPlaylist)); 468 ContentValues values = new ContentValues(); 469 String where = MediaStore.Audio.Playlists.Members._ID + "=?"; 470 String [] wherearg = new String[1]; 471 ContentResolver res = getContentResolver(); 472 473 int colidx = mTrackCursor.getColumnIndexOrThrow( 474 MediaStore.Audio.Playlists.Members.PLAY_ORDER); 475 if (from < to) { 476 // move the item to somewhere later in the list 477 mTrackCursor.moveToPosition(to); 478 int toidx = mTrackCursor.getInt(colidx); 479 mTrackCursor.moveToPosition(from); 480 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx); 481 wherearg[0] = mTrackCursor.getString(0); 482 res.update(baseUri, values, where, wherearg); 483 for (int i = from + 1; i <= to; i++) { 484 mTrackCursor.moveToPosition(i); 485 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i - 1); 486 wherearg[0] = mTrackCursor.getString(0); 487 res.update(baseUri, values, where, wherearg); 488 } 489 } else if (from > to) { 490 // move the item to somewhere earlier in the list 491 mTrackCursor.moveToPosition(to); 492 int toidx = mTrackCursor.getInt(colidx); 493 mTrackCursor.moveToPosition(from); 494 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx); 495 wherearg[0] = mTrackCursor.getString(0); 496 res.update(baseUri, values, where, wherearg); 497 for (int i = from - 1; i >= to; i--) { 498 mTrackCursor.moveToPosition(i); 499 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i + 1); 500 wherearg[0] = mTrackCursor.getString(0); 501 res.update(baseUri, values, where, wherearg); 502 } 503 } 504 } 505 } 506 }; 507 508 private TouchInterceptor.RemoveListener mRemoveListener = 509 new TouchInterceptor.RemoveListener() { 510 public void remove(int which) { 511 removePlaylistItem(which); 512 } 513 }; 514 515 private void removePlaylistItem(int which) { 516 View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition()); 517 try { 518 if (MusicUtils.sService != null 519 && which != MusicUtils.sService.getQueuePosition()) { 520 mDeletedOneRow = true; 521 } 522 } catch (RemoteException e) { 523 // Service died, so nothing playing. 524 mDeletedOneRow = true; 525 } 526 v.setVisibility(View.GONE); 527 mTrackList.invalidateViews(); 528 if (mTrackCursor instanceof NowPlayingCursor) { 529 ((NowPlayingCursor)mTrackCursor).removeItem(which); 530 } else { 531 int colidx = mTrackCursor.getColumnIndexOrThrow( 532 MediaStore.Audio.Playlists.Members._ID); 533 mTrackCursor.moveToPosition(which); 534 long id = mTrackCursor.getLong(colidx); 535 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 536 Long.valueOf(mPlaylist)); 537 getContentResolver().delete( 538 ContentUris.withAppendedId(uri, id), null, null); 539 } 540 v.setVisibility(View.VISIBLE); 541 mTrackList.invalidateViews(); 542 } 543 544 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { 545 @Override 546 public void onReceive(Context context, Intent intent) { 547 getListView().invalidateViews(); 548 } 549 }; 550 551 private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() { 552 @Override 553 public void onReceive(Context context, Intent intent) { 554 if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) { 555 getListView().invalidateViews(); 556 } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) { 557 if (mDeletedOneRow) { 558 // This is the notification for a single row that was 559 // deleted previously, which is already reflected in 560 // the UI. 561 mDeletedOneRow = false; 562 return; 563 } 564 Cursor c = new NowPlayingCursor(MusicUtils.sService, mCursorCols); 565 if (c.getCount() == 0) { 566 finish(); 567 return; 568 } 569 mAdapter.changeCursor(c); 570 } 571 } 572 }; 573 574 @Override 575 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { 576 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); 577 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); 578 MusicUtils.makePlaylistMenu(this, sub); 579 if (mEditMode) { 580 menu.add(0, REMOVE, 0, R.string.remove_from_playlist); 581 } 582 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu); 583 menu.add(0, DELETE_ITEM, 0, R.string.delete_item); 584 menu.add(0, SEARCH, 0, R.string.search_title); 585 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn; 586 mSelectedPosition = mi.position; 587 mTrackCursor.moveToPosition(mSelectedPosition); 588 try { 589 int id_idx = mTrackCursor.getColumnIndexOrThrow( 590 MediaStore.Audio.Playlists.Members.AUDIO_ID); 591 mSelectedId = mTrackCursor.getInt(id_idx); 592 } catch (IllegalArgumentException ex) { 593 mSelectedId = mi.id; 594 } 595 mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( 596 MediaStore.Audio.Media.ALBUM)); 597 mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( 598 MediaStore.Audio.Media.ARTIST)); 599 mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow( 600 MediaStore.Audio.Media.TITLE)); 601 menu.setHeaderTitle(mCurrentTrackName); 602 } 603 604 @Override 605 public boolean onContextItemSelected(MenuItem item) { 606 switch (item.getItemId()) { 607 case PLAY_SELECTION: { 608 // play the track 609 int position = mSelectedPosition; 610 MusicUtils.playAll(this, mTrackCursor, position); 611 return true; 612 } 613 614 case QUEUE: { 615 int [] list = new int[] { (int) mSelectedId }; 616 MusicUtils.addToCurrentPlaylist(this, list); 617 return true; 618 } 619 620 case NEW_PLAYLIST: { 621 Intent intent = new Intent(); 622 intent.setClass(this, CreatePlaylist.class); 623 startActivityForResult(intent, NEW_PLAYLIST); 624 return true; 625 } 626 627 case PLAYLIST_SELECTED: { 628 int [] list = new int[] { (int) mSelectedId }; 629 int playlist = item.getIntent().getIntExtra("playlist", 0); 630 MusicUtils.addToPlaylist(this, list, playlist); 631 return true; 632 } 633 634 case USE_AS_RINGTONE: 635 // Set the system setting to make this the current ringtone 636 MusicUtils.setRingtone(this, mSelectedId); 637 return true; 638 639 case DELETE_ITEM: { 640 int [] list = new int[1]; 641 list[0] = (int) mSelectedId; 642 Bundle b = new Bundle(); 643 String f = getString(R.string.delete_song_desc); 644 String desc = String.format(f, mCurrentTrackName); 645 b.putString("description", desc); 646 b.putIntArray("items", list); 647 Intent intent = new Intent(); 648 intent.setClass(this, DeleteItems.class); 649 intent.putExtras(b); 650 startActivityForResult(intent, -1); 651 return true; 652 } 653 654 case REMOVE: 655 removePlaylistItem(mSelectedPosition); 656 return true; 657 658 case SEARCH: 659 doSearch(); 660 return true; 661 } 662 return super.onContextItemSelected(item); 663 } 664 665 void doSearch() { 666 CharSequence title = null; 667 String query = null; 668 669 Intent i = new Intent(); 670 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); 671 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 672 673 title = mCurrentAlbumName; 674 query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName; 675 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); 676 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); 677 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*"); 678 title = getString(R.string.mediasearch, title); 679 i.putExtra(SearchManager.QUERY, query); 680 681 startActivity(Intent.createChooser(i, title)); 682 } 683 684 // In order to use alt-up/down as a shortcut for moving the selected item 685 // in the list, we need to override dispatchKeyEvent, not onKeyDown. 686 // (onKeyDown never sees these events, since they are handled by the list) 687 @Override 688 public boolean dispatchKeyEvent(KeyEvent event) { 689 if (mPlaylist != null && event.getMetaState() != 0 && 690 event.getAction() == KeyEvent.ACTION_DOWN) { 691 switch (event.getKeyCode()) { 692 case KeyEvent.KEYCODE_DPAD_UP: 693 moveItem(true); 694 return true; 695 case KeyEvent.KEYCODE_DPAD_DOWN: 696 moveItem(false); 697 return true; 698 case KeyEvent.KEYCODE_DEL: 699 removeItem(); 700 return true; 701 } 702 } 703 704 return super.dispatchKeyEvent(event); 705 } 706 707 private void removeItem() { 708 int curcount = mTrackCursor.getCount(); 709 int curpos = mTrackList.getSelectedItemPosition(); 710 if (curcount == 0 || curpos < 0) { 711 return; 712 } 713 714 if ("nowplaying".equals(mPlaylist)) { 715 // remove track from queue 716 717 // Work around bug 902971. To get quick visual feedback 718 // of the deletion of the item, hide the selected view. 719 try { 720 if (curpos != MusicUtils.sService.getQueuePosition()) { 721 mDeletedOneRow = true; 722 } 723 } catch (RemoteException ex) { 724 } 725 View v = mTrackList.getSelectedView(); 726 v.setVisibility(View.GONE); 727 mTrackList.invalidateViews(); 728 ((NowPlayingCursor)mTrackCursor).removeItem(curpos); 729 v.setVisibility(View.VISIBLE); 730 mTrackList.invalidateViews(); 731 } else { 732 // remove track from playlist 733 int colidx = mTrackCursor.getColumnIndexOrThrow( 734 MediaStore.Audio.Playlists.Members._ID); 735 mTrackCursor.moveToPosition(curpos); 736 long id = mTrackCursor.getLong(colidx); 737 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 738 Long.valueOf(mPlaylist)); 739 getContentResolver().delete( 740 ContentUris.withAppendedId(uri, id), null, null); 741 curcount--; 742 if (curcount == 0) { 743 finish(); 744 } else { 745 mTrackList.setSelection(curpos < curcount ? curpos : curcount); 746 } 747 } 748 } 749 750 private void moveItem(boolean up) { 751 int curcount = mTrackCursor.getCount(); 752 int curpos = mTrackList.getSelectedItemPosition(); 753 if ( (up && curpos < 1) || (!up && curpos >= curcount - 1)) { 754 return; 755 } 756 757 if (mTrackCursor instanceof NowPlayingCursor) { 758 NowPlayingCursor c = (NowPlayingCursor) mTrackCursor; 759 c.moveItem(curpos, up ? curpos - 1 : curpos + 1); 760 ((TrackListAdapter)getListAdapter()).notifyDataSetChanged(); 761 getListView().invalidateViews(); 762 mDeletedOneRow = true; 763 if (up) { 764 mTrackList.setSelection(curpos - 1); 765 } else { 766 mTrackList.setSelection(curpos + 1); 767 } 768 } else { 769 int colidx = mTrackCursor.getColumnIndexOrThrow( 770 MediaStore.Audio.Playlists.Members.PLAY_ORDER); 771 mTrackCursor.moveToPosition(curpos); 772 int currentplayidx = mTrackCursor.getInt(colidx); 773 Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external", 774 Long.valueOf(mPlaylist)); 775 ContentValues values = new ContentValues(); 776 String where = MediaStore.Audio.Playlists.Members._ID + "=?"; 777 String [] wherearg = new String[1]; 778 ContentResolver res = getContentResolver(); 779 if (up) { 780 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1); 781 wherearg[0] = mTrackCursor.getString(0); 782 res.update(baseUri, values, where, wherearg); 783 mTrackCursor.moveToPrevious(); 784 } else { 785 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1); 786 wherearg[0] = mTrackCursor.getString(0); 787 res.update(baseUri, values, where, wherearg); 788 mTrackCursor.moveToNext(); 789 } 790 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx); 791 wherearg[0] = mTrackCursor.getString(0); 792 res.update(baseUri, values, where, wherearg); 793 } 794 } 795 796 @Override 797 protected void onListItemClick(ListView l, View v, int position, long id) 798 { 799 if (mTrackCursor.getCount() == 0) { 800 return; 801 } 802 MusicUtils.playAll(this, mTrackCursor, position); 803 } 804 805 @Override 806 public boolean onCreateOptionsMenu(Menu menu) { 807 /* This activity is used for a number of different browsing modes, and the menu can 808 * be different for each of them: 809 * - all tracks, optionally restricted to an album, artist or playlist 810 * - the list of currently playing songs 811 */ 812 super.onCreateOptionsMenu(menu); 813 if (mPlaylist == null) { 814 menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(com.android.internal.R.drawable.ic_menu_play_clip); 815 } 816 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library); 817 menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback) 818 .setVisible(MusicUtils.isMusicLoaded()); 819 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); 820 if (mPlaylist != null) { 821 menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(android.R.drawable.ic_menu_save); 822 if (mPlaylist.equals("nowplaying")) { 823 menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(com.android.internal.R.drawable.ic_menu_clear_playlist); 824 } 825 } 826 return true; 827 } 828 829 @Override 830 public boolean onOptionsItemSelected(MenuItem item) { 831 Intent intent; 832 Cursor cursor; 833 switch (item.getItemId()) { 834 case PLAY_ALL: { 835 MusicUtils.playAll(this, mTrackCursor); 836 return true; 837 } 838 839 case GOTO_START: 840 intent = new Intent(); 841 intent.setClass(this, MusicBrowserActivity.class); 842 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 843 startActivity(intent); 844 return true; 845 846 case GOTO_PLAYBACK: 847 intent = new Intent("com.android.music.PLAYBACK_VIEWER"); 848 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 849 startActivity(intent); 850 return true; 851 852 case SHUFFLE_ALL: 853 // Should 'shuffle all' shuffle ALL, or only the tracks shown? 854 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 855 new String [] { MediaStore.Audio.Media._ID}, 856 MediaStore.Audio.Media.IS_MUSIC + "=1", null, 857 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 858 if (cursor != null) { 859 MusicUtils.shuffleAll(this, cursor); 860 cursor.close(); 861 } 862 return true; 863 864 case SAVE_AS_PLAYLIST: 865 intent = new Intent(); 866 intent.setClass(this, CreatePlaylist.class); 867 startActivityForResult(intent, SAVE_AS_PLAYLIST); 868 return true; 869 870 case CLEAR_PLAYLIST: 871 // We only clear the current playlist 872 MusicUtils.clearQueue(); 873 return true; 874 } 875 return super.onOptionsItemSelected(item); 876 } 877 878 @Override 879 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 880 switch (requestCode) { 881 case SCAN_DONE: 882 if (resultCode == RESULT_CANCELED) { 883 finish(); 884 } else { 885 getTrackCursor(mAdapter.getQueryHandler(), null); 886 } 887 break; 888 889 case NEW_PLAYLIST: 890 if (resultCode == RESULT_OK) { 891 Uri uri = intent.getData(); 892 if (uri != null) { 893 int [] list = new int[] { (int) mSelectedId }; 894 MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment())); 895 } 896 } 897 break; 898 899 case SAVE_AS_PLAYLIST: 900 if (resultCode == RESULT_OK) { 901 Uri uri = intent.getData(); 902 if (uri != null) { 903 int [] list = MusicUtils.getSongListForCursor(mTrackCursor); 904 int plid = Integer.parseInt(uri.getLastPathSegment()); 905 MusicUtils.addToPlaylist(this, list, plid); 906 } 907 } 908 break; 909 } 910 } 911 912 private Cursor getTrackCursor(AsyncQueryHandler async, String filter) { 913 Cursor ret = null; 914 mSortOrder = MediaStore.Audio.Media.TITLE_KEY; 915 StringBuilder where = new StringBuilder(); 916 where.append(MediaStore.Audio.Media.TITLE + " != ''"); 917 918 // Add in the filtering constraints 919 String [] keywords = null; 920 if (filter != null) { 921 String [] searchWords = filter.split(" "); 922 keywords = new String[searchWords.length]; 923 Collator col = Collator.getInstance(); 924 col.setStrength(Collator.PRIMARY); 925 for (int i = 0; i < searchWords.length; i++) { 926 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%'; 927 } 928 for (int i = 0; i < searchWords.length; i++) { 929 where.append(" AND "); 930 where.append(MediaStore.Audio.Media.ARTIST_KEY + "||"); 931 where.append(MediaStore.Audio.Media.ALBUM_KEY + "||"); 932 where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?"); 933 } 934 } 935 936 if (mGenre != null) { 937 mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER; 938 if (async != null) { 939 async.startQuery(0, null, 940 MediaStore.Audio.Genres.Members.getContentUri("external", 941 Integer.valueOf(mGenre)), 942 mCursorCols, where.toString(), keywords, mSortOrder); 943 ret = null; 944 } else { 945 ret = MusicUtils.query(this, 946 MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)), 947 mCursorCols, where.toString(), keywords, mSortOrder); 948 } 949 } else if (mPlaylist != null) { 950 if (mPlaylist.equals("nowplaying")) { 951 if (MusicUtils.sService != null) { 952 ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols); 953 if (ret.getCount() == 0) { 954 finish(); 955 } 956 } else { 957 // Nothing is playing. 958 } 959 } else if (mPlaylist.equals("podcasts")) { 960 where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1"); 961 if (async != null) { 962 async.startQuery(0, null, 963 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 964 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 965 ret = null; 966 } else { 967 ret = MusicUtils.query(this, 968 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 969 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 970 } 971 } else if (mPlaylist.equals("recentlyadded")) { 972 // do a query for all songs added in the last X weeks 973 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7); 974 where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">"); 975 where.append(System.currentTimeMillis() / 1000 - X); 976 if (async != null) { 977 async.startQuery(0, null, 978 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 979 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 980 ret = null; 981 } else { 982 ret = MusicUtils.query(this, 983 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 984 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 985 } 986 } else { 987 mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER; 988 if (async != null) { 989 async.startQuery(0, null, 990 MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)), 991 mPlaylistMemberCols, where.toString(), keywords, mSortOrder); 992 ret = null; 993 } else { 994 ret = MusicUtils.query(this, 995 MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)), 996 mPlaylistMemberCols, where.toString(), keywords, mSortOrder); 997 } 998 } 999 } else { 1000 if (mAlbumId != null) { 1001 where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId); 1002 mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder; 1003 } 1004 if (mArtistId != null) { 1005 where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId); 1006 } 1007 where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1"); 1008 if (async != null) { 1009 async.startQuery(0, null, 1010 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1011 mCursorCols, where.toString() , keywords, mSortOrder); 1012 ret = null; 1013 } else { 1014 ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1015 mCursorCols, where.toString() , keywords, mSortOrder); 1016 } 1017 } 1018 1019 // This special case is for the "nowplaying" cursor, which cannot be handled 1020 // asynchronously using AsyncQueryHandler, so we do some extra initialization here. 1021 if (ret != null && async != null) { 1022 init(ret); 1023 setTitle(); 1024 } 1025 return ret; 1026 } 1027 1028 private class NowPlayingCursor extends AbstractCursor 1029 { 1030 public NowPlayingCursor(IMediaPlaybackService service, String [] cols) 1031 { 1032 mCols = cols; 1033 mService = service; 1034 makeNowPlayingCursor(); 1035 } 1036 private void makeNowPlayingCursor() { 1037 mCurrentPlaylistCursor = null; 1038 try { 1039 mNowPlaying = mService.getQueue(); 1040 } catch (RemoteException ex) { 1041 mNowPlaying = new int[0]; 1042 } 1043 mSize = mNowPlaying.length; 1044 if (mSize == 0) { 1045 return; 1046 } 1047 1048 StringBuilder where = new StringBuilder(); 1049 where.append(MediaStore.Audio.Media._ID + " IN ("); 1050 for (int i = 0; i < mSize; i++) { 1051 where.append(mNowPlaying[i]); 1052 if (i < mSize - 1) { 1053 where.append(","); 1054 } 1055 } 1056 where.append(")"); 1057 1058 mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this, 1059 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1060 mCols, where.toString(), null, MediaStore.Audio.Media._ID); 1061 1062 if (mCurrentPlaylistCursor == null) { 1063 mSize = 0; 1064 return; 1065 } 1066 1067 int size = mCurrentPlaylistCursor.getCount(); 1068 mCursorIdxs = new int[size]; 1069 mCurrentPlaylistCursor.moveToFirst(); 1070 int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 1071 for (int i = 0; i < size; i++) { 1072 mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx); 1073 mCurrentPlaylistCursor.moveToNext(); 1074 } 1075 mCurrentPlaylistCursor.moveToFirst(); 1076 mCurPos = -1; 1077 1078 // At this point we can verify the 'now playing' list we got 1079 // earlier to make sure that all the items in there still exist 1080 // in the database, and remove those that aren't. This way we 1081 // don't get any blank items in the list. 1082 try { 1083 int removed = 0; 1084 for (int i = mNowPlaying.length - 1; i >= 0; i--) { 1085 int trackid = mNowPlaying[i]; 1086 int crsridx = Arrays.binarySearch(mCursorIdxs, trackid); 1087 if (crsridx < 0) { 1088 //Log.i("@@@@@", "item no longer exists in db: " + trackid); 1089 removed += mService.removeTrack(trackid); 1090 } 1091 } 1092 if (removed > 0) { 1093 mNowPlaying = mService.getQueue(); 1094 mSize = mNowPlaying.length; 1095 if (mSize == 0) { 1096 mCursorIdxs = null; 1097 return; 1098 } 1099 } 1100 } catch (RemoteException ex) { 1101 mNowPlaying = new int[0]; 1102 } 1103 } 1104 1105 @Override 1106 public int getCount() 1107 { 1108 return mSize; 1109 } 1110 1111 @Override 1112 public boolean onMove(int oldPosition, int newPosition) 1113 { 1114 if (oldPosition == newPosition) 1115 return true; 1116 1117 if (mNowPlaying == null || mCursorIdxs == null) { 1118 return false; 1119 } 1120 1121 // The cursor doesn't have any duplicates in it, and is not ordered 1122 // in queue-order, so we need to figure out where in the cursor we 1123 // should be. 1124 1125 int newid = mNowPlaying[newPosition]; 1126 int crsridx = Arrays.binarySearch(mCursorIdxs, newid); 1127 mCurrentPlaylistCursor.moveToPosition(crsridx); 1128 mCurPos = newPosition; 1129 1130 return true; 1131 } 1132 1133 public boolean removeItem(int which) 1134 { 1135 try { 1136 if (mService.removeTracks(which, which) == 0) { 1137 return false; // delete failed 1138 } 1139 int i = (int) which; 1140 mSize--; 1141 while (i < mSize) { 1142 mNowPlaying[i] = mNowPlaying[i+1]; 1143 i++; 1144 } 1145 onMove(-1, (int) mCurPos); 1146 } catch (RemoteException ex) { 1147 } 1148 return true; 1149 } 1150 1151 public void moveItem(int from, int to) { 1152 try { 1153 mService.moveQueueItem(from, to); 1154 mNowPlaying = mService.getQueue(); 1155 onMove(-1, mCurPos); // update the underlying cursor 1156 } catch (RemoteException ex) { 1157 } 1158 } 1159 1160 private void dump() { 1161 String where = "("; 1162 for (int i = 0; i < mSize; i++) { 1163 where += mNowPlaying[i]; 1164 if (i < mSize - 1) { 1165 where += ","; 1166 } 1167 } 1168 where += ")"; 1169 Log.i("NowPlayingCursor: ", where); 1170 } 1171 1172 @Override 1173 public String getString(int column) 1174 { 1175 try { 1176 return mCurrentPlaylistCursor.getString(column); 1177 } catch (Exception ex) { 1178 onChange(true); 1179 return ""; 1180 } 1181 } 1182 1183 @Override 1184 public short getShort(int column) 1185 { 1186 return mCurrentPlaylistCursor.getShort(column); 1187 } 1188 1189 @Override 1190 public int getInt(int column) 1191 { 1192 try { 1193 return mCurrentPlaylistCursor.getInt(column); 1194 } catch (Exception ex) { 1195 onChange(true); 1196 return 0; 1197 } 1198 } 1199 1200 @Override 1201 public long getLong(int column) 1202 { 1203 try { 1204 return mCurrentPlaylistCursor.getLong(column); 1205 } catch (Exception ex) { 1206 onChange(true); 1207 return 0; 1208 } 1209 } 1210 1211 @Override 1212 public float getFloat(int column) 1213 { 1214 return mCurrentPlaylistCursor.getFloat(column); 1215 } 1216 1217 @Override 1218 public double getDouble(int column) 1219 { 1220 return mCurrentPlaylistCursor.getDouble(column); 1221 } 1222 1223 @Override 1224 public boolean isNull(int column) 1225 { 1226 return mCurrentPlaylistCursor.isNull(column); 1227 } 1228 1229 @Override 1230 public String[] getColumnNames() 1231 { 1232 return mCols; 1233 } 1234 1235 @Override 1236 public void deactivate() 1237 { 1238 if (mCurrentPlaylistCursor != null) 1239 mCurrentPlaylistCursor.deactivate(); 1240 } 1241 1242 @Override 1243 public boolean requery() 1244 { 1245 makeNowPlayingCursor(); 1246 return true; 1247 } 1248 1249 private String [] mCols; 1250 private Cursor mCurrentPlaylistCursor; // updated in onMove 1251 private int mSize; // size of the queue 1252 private int[] mNowPlaying; 1253 private int[] mCursorIdxs; 1254 private int mCurPos; 1255 private IMediaPlaybackService mService; 1256 } 1257 1258 static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer { 1259 boolean mIsNowPlaying; 1260 boolean mDisableNowPlayingIndicator; 1261 1262 int mTitleIdx; 1263 int mArtistIdx; 1264 int mAlbumIdx; 1265 int mDurationIdx; 1266 int mAudioIdIdx; 1267 1268 private final StringBuilder mBuilder = new StringBuilder(); 1269 private final String mUnknownArtist; 1270 private final String mUnknownAlbum; 1271 1272 private AlphabetIndexer mIndexer; 1273 1274 private TrackBrowserActivity mActivity = null; 1275 private AsyncQueryHandler mQueryHandler; 1276 private String mConstraint = null; 1277 private boolean mConstraintIsValid = false; 1278 1279 class ViewHolder { 1280 TextView line1; 1281 TextView line2; 1282 TextView duration; 1283 ImageView play_indicator; 1284 CharArrayBuffer buffer1; 1285 char [] buffer2; 1286 } 1287 1288 class QueryHandler extends AsyncQueryHandler { 1289 QueryHandler(ContentResolver res) { 1290 super(res); 1291 } 1292 1293 @Override 1294 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 1295 //Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity); 1296 mActivity.init(cursor); 1297 } 1298 } 1299 1300 TrackListAdapter(Context context, TrackBrowserActivity currentactivity, 1301 int layout, Cursor cursor, String[] from, int[] to, 1302 boolean isnowplaying, boolean disablenowplayingindicator) { 1303 super(context, layout, cursor, from, to); 1304 mActivity = currentactivity; 1305 getColumnIndices(cursor); 1306 mIsNowPlaying = isnowplaying; 1307 mDisableNowPlayingIndicator = disablenowplayingindicator; 1308 mUnknownArtist = context.getString(R.string.unknown_artist_name); 1309 mUnknownAlbum = context.getString(R.string.unknown_album_name); 1310 1311 mQueryHandler = new QueryHandler(context.getContentResolver()); 1312 } 1313 1314 public void setActivity(TrackBrowserActivity newactivity) { 1315 mActivity = newactivity; 1316 } 1317 1318 public AsyncQueryHandler getQueryHandler() { 1319 return mQueryHandler; 1320 } 1321 1322 private void getColumnIndices(Cursor cursor) { 1323 if (cursor != null) { 1324 mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); 1325 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST); 1326 mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM); 1327 mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION); 1328 try { 1329 mAudioIdIdx = cursor.getColumnIndexOrThrow( 1330 MediaStore.Audio.Playlists.Members.AUDIO_ID); 1331 } catch (IllegalArgumentException ex) { 1332 mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 1333 } 1334 1335 if (mIndexer != null) { 1336 mIndexer.setCursor(cursor); 1337 } else if (!mActivity.mEditMode) { 1338 String alpha = mActivity.getString( 1339 com.android.internal.R.string.fast_scroll_alphabet); 1340 1341 mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha); 1342 } 1343 } 1344 } 1345 1346 @Override 1347 public View newView(Context context, Cursor cursor, ViewGroup parent) { 1348 View v = super.newView(context, cursor, parent); 1349 ImageView iv = (ImageView) v.findViewById(R.id.icon); 1350 if (mActivity.mEditMode) { 1351 iv.setVisibility(View.VISIBLE); 1352 iv.setImageResource(R.drawable.ic_mp_move); 1353 } else { 1354 iv.setVisibility(View.GONE); 1355 } 1356 1357 ViewHolder vh = new ViewHolder(); 1358 vh.line1 = (TextView) v.findViewById(R.id.line1); 1359 vh.line2 = (TextView) v.findViewById(R.id.line2); 1360 vh.duration = (TextView) v.findViewById(R.id.duration); 1361 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 1362 vh.buffer1 = new CharArrayBuffer(100); 1363 vh.buffer2 = new char[200]; 1364 v.setTag(vh); 1365 return v; 1366 } 1367 1368 @Override 1369 public void bindView(View view, Context context, Cursor cursor) { 1370 1371 ViewHolder vh = (ViewHolder) view.getTag(); 1372 1373 cursor.copyStringToBuffer(mTitleIdx, vh.buffer1); 1374 vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied); 1375 1376 int secs = cursor.getInt(mDurationIdx) / 1000; 1377 if (secs == 0) { 1378 vh.duration.setText(""); 1379 } else { 1380 vh.duration.setText(MusicUtils.makeTimeString(context, secs)); 1381 } 1382 1383 final StringBuilder builder = mBuilder; 1384 builder.delete(0, builder.length()); 1385 1386 String name = cursor.getString(mArtistIdx); 1387 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 1388 builder.append(mUnknownArtist); 1389 } else { 1390 builder.append(name); 1391 } 1392 int len = builder.length(); 1393 if (vh.buffer2.length < len) { 1394 vh.buffer2 = new char[len]; 1395 } 1396 builder.getChars(0, len, vh.buffer2, 0); 1397 vh.line2.setText(vh.buffer2, 0, len); 1398 1399 ImageView iv = vh.play_indicator; 1400 int id = -1; 1401 if (MusicUtils.sService != null) { 1402 // TODO: IPC call on each bind?? 1403 try { 1404 if (mIsNowPlaying) { 1405 id = MusicUtils.sService.getQueuePosition(); 1406 } else { 1407 id = MusicUtils.sService.getAudioId(); 1408 } 1409 } catch (RemoteException ex) { 1410 } 1411 } 1412 1413 // Determining whether and where to show the "now playing indicator 1414 // is tricky, because we don't actually keep track of where the songs 1415 // in the current playlist came from after they've started playing. 1416 // 1417 // If the "current playlists" is shown, then we can simply match by position, 1418 // otherwise, we need to match by id. Match-by-id gets a little weird if 1419 // a song appears in a playlist more than once, and you're in edit-playlist 1420 // mode. In that case, both items will have the "now playing" indicator. 1421 // For this reason, we don't show the play indicator at all when in edit 1422 // playlist mode (except when you're viewing the "current playlist", 1423 // which is not really a playlist) 1424 if ( (mIsNowPlaying && cursor.getPosition() == id) || 1425 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getInt(mAudioIdIdx) == id)) { 1426 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list); 1427 iv.setVisibility(View.VISIBLE); 1428 } else { 1429 iv.setVisibility(View.GONE); 1430 } 1431 } 1432 1433 @Override 1434 public void changeCursor(Cursor cursor) { 1435 if (cursor != mActivity.mTrackCursor) { 1436 mActivity.mTrackCursor = cursor; 1437 super.changeCursor(cursor); 1438 getColumnIndices(cursor); 1439 } 1440 } 1441 1442 @Override 1443 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 1444 String s = constraint.toString(); 1445 if (mConstraintIsValid && ( 1446 (s == null && mConstraint == null) || 1447 (s != null && s.equals(mConstraint)))) { 1448 return getCursor(); 1449 } 1450 Cursor c = mActivity.getTrackCursor(null, s); 1451 mConstraint = s; 1452 mConstraintIsValid = true; 1453 return c; 1454 } 1455 1456 // SectionIndexer methods 1457 1458 public Object[] getSections() { 1459 if (mIndexer != null) { 1460 return mIndexer.getSections(); 1461 } else { 1462 return null; 1463 } 1464 } 1465 1466 public int getPositionForSection(int section) { 1467 int pos = mIndexer.getPositionForSection(section); 1468 return pos; 1469 } 1470 1471 public int getSectionForPosition(int position) { 1472 return 0; 1473 } 1474 } 1475} 1476 1477