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