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