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