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