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