TrackBrowserActivity.java revision 756c3f57f11a7bb3bf1cf1333ee8ffbc51039748
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.ALBUM_KEY + "||"); 919 where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?"); 920 } 921 } 922 923 if (mGenre != null) { 924 mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER; 925 if (async != null) { 926 async.startQuery(0, null, 927 MediaStore.Audio.Genres.Members.getContentUri("external", 928 Integer.valueOf(mGenre)), 929 mCursorCols, where.toString(), keywords, mSortOrder); 930 ret = null; 931 } else { 932 ret = MusicUtils.query(this, 933 MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)), 934 mCursorCols, where.toString(), keywords, mSortOrder); 935 } 936 } else if (mPlaylist != null) { 937 if (mPlaylist.equals("nowplaying")) { 938 if (MusicUtils.sService != null) { 939 ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols); 940 if (ret.getCount() == 0) { 941 finish(); 942 } 943 } else { 944 // Nothing is playing. 945 } 946 } else if (mPlaylist.equals("podcasts")) { 947 where.append(" AND " + MediaStore.Audio.Media.IS_PODCAST + "=1"); 948 if (async != null) { 949 async.startQuery(0, null, 950 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 951 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 952 ret = null; 953 } else { 954 ret = MusicUtils.query(this, 955 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 956 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 957 } 958 } else if (mPlaylist.equals("recentlyadded")) { 959 // do a query for all songs added in the last X weeks 960 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7); 961 where.append(" AND " + MediaStore.MediaColumns.DATE_ADDED + ">"); 962 where.append(System.currentTimeMillis() / 1000 - X); 963 if (async != null) { 964 async.startQuery(0, null, 965 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 966 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 967 ret = null; 968 } else { 969 ret = MusicUtils.query(this, 970 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursorCols, 971 where.toString(), keywords, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 972 } 973 } else { 974 mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER; 975 if (async != null) { 976 async.startQuery(0, null, 977 MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)), 978 mPlaylistMemberCols, where.toString(), keywords, mSortOrder); 979 ret = null; 980 } else { 981 ret = MusicUtils.query(this, 982 MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)), 983 mPlaylistMemberCols, where.toString(), keywords, mSortOrder); 984 } 985 } 986 } else { 987 if (mAlbumId != null) { 988 where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId); 989 mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder; 990 } 991 if (mArtistId != null) { 992 where.append(" AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + mArtistId); 993 } 994 where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1"); 995 if (async != null) { 996 async.startQuery(0, null, 997 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 998 mCursorCols, where.toString() , keywords, mSortOrder); 999 ret = null; 1000 } else { 1001 ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1002 mCursorCols, where.toString() , keywords, mSortOrder); 1003 } 1004 } 1005 1006 // This special case is for the "nowplaying" cursor, which cannot be handled 1007 // asynchronously using AsyncQueryHandler, so we do some extra initialization here. 1008 if (ret != null && async != null) { 1009 init(ret); 1010 setTitle(); 1011 } 1012 return ret; 1013 } 1014 1015 private class NowPlayingCursor extends AbstractCursor 1016 { 1017 public NowPlayingCursor(IMediaPlaybackService service, String [] cols) 1018 { 1019 mCols = cols; 1020 mService = service; 1021 makeNowPlayingCursor(); 1022 } 1023 private void makeNowPlayingCursor() { 1024 mCurrentPlaylistCursor = null; 1025 try { 1026 mNowPlaying = mService.getQueue(); 1027 } catch (RemoteException ex) { 1028 mNowPlaying = new int[0]; 1029 } 1030 mSize = mNowPlaying.length; 1031 if (mSize == 0) { 1032 return; 1033 } 1034 1035 StringBuilder where = new StringBuilder(); 1036 where.append(MediaStore.Audio.Media._ID + " IN ("); 1037 for (int i = 0; i < mSize; i++) { 1038 where.append(mNowPlaying[i]); 1039 if (i < mSize - 1) { 1040 where.append(","); 1041 } 1042 } 1043 where.append(")"); 1044 1045 mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this, 1046 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 1047 mCols, where.toString(), null, MediaStore.Audio.Media._ID); 1048 1049 if (mCurrentPlaylistCursor == null) { 1050 mSize = 0; 1051 return; 1052 } 1053 1054 int size = mCurrentPlaylistCursor.getCount(); 1055 mCursorIdxs = new int[size]; 1056 mCurrentPlaylistCursor.moveToFirst(); 1057 int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 1058 for (int i = 0; i < size; i++) { 1059 mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx); 1060 mCurrentPlaylistCursor.moveToNext(); 1061 } 1062 mCurrentPlaylistCursor.moveToFirst(); 1063 mCurPos = -1; 1064 1065 // At this point we can verify the 'now playing' list we got 1066 // earlier to make sure that all the items in there still exist 1067 // in the database, and remove those that aren't. This way we 1068 // don't get any blank items in the list. 1069 try { 1070 int removed = 0; 1071 for (int i = mNowPlaying.length - 1; i >= 0; i--) { 1072 int trackid = mNowPlaying[i]; 1073 int crsridx = Arrays.binarySearch(mCursorIdxs, trackid); 1074 if (crsridx < 0) { 1075 //Log.i("@@@@@", "item no longer exists in db: " + trackid); 1076 removed += mService.removeTrack(trackid); 1077 } 1078 } 1079 if (removed > 0) { 1080 mNowPlaying = mService.getQueue(); 1081 mSize = mNowPlaying.length; 1082 if (mSize == 0) { 1083 mCursorIdxs = null; 1084 return; 1085 } 1086 } 1087 } catch (RemoteException ex) { 1088 mNowPlaying = new int[0]; 1089 } 1090 } 1091 1092 @Override 1093 public int getCount() 1094 { 1095 return mSize; 1096 } 1097 1098 @Override 1099 public boolean onMove(int oldPosition, int newPosition) 1100 { 1101 if (oldPosition == newPosition) 1102 return true; 1103 1104 if (mNowPlaying == null || mCursorIdxs == null) { 1105 return false; 1106 } 1107 1108 // The cursor doesn't have any duplicates in it, and is not ordered 1109 // in queue-order, so we need to figure out where in the cursor we 1110 // should be. 1111 1112 int newid = mNowPlaying[newPosition]; 1113 int crsridx = Arrays.binarySearch(mCursorIdxs, newid); 1114 mCurrentPlaylistCursor.moveToPosition(crsridx); 1115 mCurPos = newPosition; 1116 1117 return true; 1118 } 1119 1120 public boolean removeItem(int which) 1121 { 1122 try { 1123 if (mService.removeTracks(which, which) == 0) { 1124 return false; // delete failed 1125 } 1126 int i = (int) which; 1127 mSize--; 1128 while (i < mSize) { 1129 mNowPlaying[i] = mNowPlaying[i+1]; 1130 i++; 1131 } 1132 onMove(-1, (int) mCurPos); 1133 } catch (RemoteException ex) { 1134 } 1135 return true; 1136 } 1137 1138 public void moveItem(int from, int to) { 1139 try { 1140 mService.moveQueueItem(from, to); 1141 mNowPlaying = mService.getQueue(); 1142 onMove(-1, mCurPos); // update the underlying cursor 1143 } catch (RemoteException ex) { 1144 } 1145 } 1146 1147 private void dump() { 1148 String where = "("; 1149 for (int i = 0; i < mSize; i++) { 1150 where += mNowPlaying[i]; 1151 if (i < mSize - 1) { 1152 where += ","; 1153 } 1154 } 1155 where += ")"; 1156 Log.i("NowPlayingCursor: ", where); 1157 } 1158 1159 @Override 1160 public String getString(int column) 1161 { 1162 try { 1163 return mCurrentPlaylistCursor.getString(column); 1164 } catch (Exception ex) { 1165 onChange(true); 1166 return ""; 1167 } 1168 } 1169 1170 @Override 1171 public short getShort(int column) 1172 { 1173 return mCurrentPlaylistCursor.getShort(column); 1174 } 1175 1176 @Override 1177 public int getInt(int column) 1178 { 1179 try { 1180 return mCurrentPlaylistCursor.getInt(column); 1181 } catch (Exception ex) { 1182 onChange(true); 1183 return 0; 1184 } 1185 } 1186 1187 @Override 1188 public long getLong(int column) 1189 { 1190 try { 1191 return mCurrentPlaylistCursor.getLong(column); 1192 } catch (Exception ex) { 1193 onChange(true); 1194 return 0; 1195 } 1196 } 1197 1198 @Override 1199 public float getFloat(int column) 1200 { 1201 return mCurrentPlaylistCursor.getFloat(column); 1202 } 1203 1204 @Override 1205 public double getDouble(int column) 1206 { 1207 return mCurrentPlaylistCursor.getDouble(column); 1208 } 1209 1210 @Override 1211 public boolean isNull(int column) 1212 { 1213 return mCurrentPlaylistCursor.isNull(column); 1214 } 1215 1216 @Override 1217 public String[] getColumnNames() 1218 { 1219 return mCols; 1220 } 1221 1222 @Override 1223 public void deactivate() 1224 { 1225 if (mCurrentPlaylistCursor != null) 1226 mCurrentPlaylistCursor.deactivate(); 1227 } 1228 1229 @Override 1230 public boolean requery() 1231 { 1232 makeNowPlayingCursor(); 1233 return true; 1234 } 1235 1236 private String [] mCols; 1237 private Cursor mCurrentPlaylistCursor; // updated in onMove 1238 private int mSize; // size of the queue 1239 private int[] mNowPlaying; 1240 private int[] mCursorIdxs; 1241 private int mCurPos; 1242 private IMediaPlaybackService mService; 1243 } 1244 1245 static class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer { 1246 boolean mIsNowPlaying; 1247 boolean mDisableNowPlayingIndicator; 1248 1249 int mTitleIdx; 1250 int mArtistIdx; 1251 int mDurationIdx; 1252 int mAudioIdIdx; 1253 1254 private final StringBuilder mBuilder = new StringBuilder(); 1255 private final String mUnknownArtist; 1256 private final String mUnknownAlbum; 1257 1258 private AlphabetIndexer mIndexer; 1259 1260 private TrackBrowserActivity mActivity = null; 1261 private AsyncQueryHandler mQueryHandler; 1262 private String mConstraint = null; 1263 private boolean mConstraintIsValid = false; 1264 1265 static class ViewHolder { 1266 TextView line1; 1267 TextView line2; 1268 TextView duration; 1269 ImageView play_indicator; 1270 CharArrayBuffer buffer1; 1271 char [] buffer2; 1272 } 1273 1274 class QueryHandler extends AsyncQueryHandler { 1275 QueryHandler(ContentResolver res) { 1276 super(res); 1277 } 1278 1279 @Override 1280 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 1281 //Log.i("@@@", "query complete: " + cursor.getCount() + " " + mActivity); 1282 mActivity.init(cursor); 1283 } 1284 } 1285 1286 TrackListAdapter(Context context, TrackBrowserActivity currentactivity, 1287 int layout, Cursor cursor, String[] from, int[] to, 1288 boolean isnowplaying, boolean disablenowplayingindicator) { 1289 super(context, layout, cursor, from, to); 1290 mActivity = currentactivity; 1291 getColumnIndices(cursor); 1292 mIsNowPlaying = isnowplaying; 1293 mDisableNowPlayingIndicator = disablenowplayingindicator; 1294 mUnknownArtist = context.getString(R.string.unknown_artist_name); 1295 mUnknownAlbum = context.getString(R.string.unknown_album_name); 1296 1297 mQueryHandler = new QueryHandler(context.getContentResolver()); 1298 } 1299 1300 public void setActivity(TrackBrowserActivity newactivity) { 1301 mActivity = newactivity; 1302 } 1303 1304 public AsyncQueryHandler getQueryHandler() { 1305 return mQueryHandler; 1306 } 1307 1308 private void getColumnIndices(Cursor cursor) { 1309 if (cursor != null) { 1310 mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); 1311 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST); 1312 mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION); 1313 try { 1314 mAudioIdIdx = cursor.getColumnIndexOrThrow( 1315 MediaStore.Audio.Playlists.Members.AUDIO_ID); 1316 } catch (IllegalArgumentException ex) { 1317 mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); 1318 } 1319 1320 if (mIndexer != null) { 1321 mIndexer.setCursor(cursor); 1322 } else if (!mActivity.mEditMode) { 1323 String alpha = mActivity.getString( 1324 com.android.internal.R.string.fast_scroll_alphabet); 1325 1326 mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, alpha); 1327 } 1328 } 1329 } 1330 1331 @Override 1332 public View newView(Context context, Cursor cursor, ViewGroup parent) { 1333 View v = super.newView(context, cursor, parent); 1334 ImageView iv = (ImageView) v.findViewById(R.id.icon); 1335 if (mActivity.mEditMode) { 1336 iv.setVisibility(View.VISIBLE); 1337 iv.setImageResource(R.drawable.ic_mp_move); 1338 } else { 1339 iv.setVisibility(View.GONE); 1340 } 1341 1342 ViewHolder vh = new ViewHolder(); 1343 vh.line1 = (TextView) v.findViewById(R.id.line1); 1344 vh.line2 = (TextView) v.findViewById(R.id.line2); 1345 vh.duration = (TextView) v.findViewById(R.id.duration); 1346 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 1347 vh.buffer1 = new CharArrayBuffer(100); 1348 vh.buffer2 = new char[200]; 1349 v.setTag(vh); 1350 return v; 1351 } 1352 1353 @Override 1354 public void bindView(View view, Context context, Cursor cursor) { 1355 1356 ViewHolder vh = (ViewHolder) view.getTag(); 1357 1358 cursor.copyStringToBuffer(mTitleIdx, vh.buffer1); 1359 vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied); 1360 1361 int secs = cursor.getInt(mDurationIdx) / 1000; 1362 if (secs == 0) { 1363 vh.duration.setText(""); 1364 } else { 1365 vh.duration.setText(MusicUtils.makeTimeString(context, secs)); 1366 } 1367 1368 final StringBuilder builder = mBuilder; 1369 builder.delete(0, builder.length()); 1370 1371 String name = cursor.getString(mArtistIdx); 1372 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 1373 builder.append(mUnknownArtist); 1374 } else { 1375 builder.append(name); 1376 } 1377 int len = builder.length(); 1378 if (vh.buffer2.length < len) { 1379 vh.buffer2 = new char[len]; 1380 } 1381 builder.getChars(0, len, vh.buffer2, 0); 1382 vh.line2.setText(vh.buffer2, 0, len); 1383 1384 ImageView iv = vh.play_indicator; 1385 int id = -1; 1386 if (MusicUtils.sService != null) { 1387 // TODO: IPC call on each bind?? 1388 try { 1389 if (mIsNowPlaying) { 1390 id = MusicUtils.sService.getQueuePosition(); 1391 } else { 1392 id = MusicUtils.sService.getAudioId(); 1393 } 1394 } catch (RemoteException ex) { 1395 } 1396 } 1397 1398 // Determining whether and where to show the "now playing indicator 1399 // is tricky, because we don't actually keep track of where the songs 1400 // in the current playlist came from after they've started playing. 1401 // 1402 // If the "current playlists" is shown, then we can simply match by position, 1403 // otherwise, we need to match by id. Match-by-id gets a little weird if 1404 // a song appears in a playlist more than once, and you're in edit-playlist 1405 // mode. In that case, both items will have the "now playing" indicator. 1406 // For this reason, we don't show the play indicator at all when in edit 1407 // playlist mode (except when you're viewing the "current playlist", 1408 // which is not really a playlist) 1409 if ( (mIsNowPlaying && cursor.getPosition() == id) || 1410 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getInt(mAudioIdIdx) == id)) { 1411 iv.setImageResource(R.drawable.indicator_ic_mp_playing_list); 1412 iv.setVisibility(View.VISIBLE); 1413 } else { 1414 iv.setVisibility(View.GONE); 1415 } 1416 } 1417 1418 @Override 1419 public void changeCursor(Cursor cursor) { 1420 if (cursor != mActivity.mTrackCursor) { 1421 mActivity.mTrackCursor = cursor; 1422 super.changeCursor(cursor); 1423 getColumnIndices(cursor); 1424 } 1425 } 1426 1427 @Override 1428 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 1429 String s = constraint.toString(); 1430 if (mConstraintIsValid && ( 1431 (s == null && mConstraint == null) || 1432 (s != null && s.equals(mConstraint)))) { 1433 return getCursor(); 1434 } 1435 Cursor c = mActivity.getTrackCursor(null, s); 1436 mConstraint = s; 1437 mConstraintIsValid = true; 1438 return c; 1439 } 1440 1441 // SectionIndexer methods 1442 1443 public Object[] getSections() { 1444 if (mIndexer != null) { 1445 return mIndexer.getSections(); 1446 } else { 1447 return null; 1448 } 1449 } 1450 1451 public int getPositionForSection(int section) { 1452 int pos = mIndexer.getPositionForSection(section); 1453 return pos; 1454 } 1455 1456 public int getSectionForPosition(int position) { 1457 return 0; 1458 } 1459 } 1460} 1461 1462