AlbumBrowserActivity.java revision 0164ebfdcf374bc2b65e7c86d02b597059c25e44
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.music; 18 19import android.app.ListActivity; 20import android.app.SearchManager; 21import android.content.AsyncQueryHandler; 22import android.content.BroadcastReceiver; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.res.Resources; 28import android.database.Cursor; 29import android.graphics.Bitmap; 30import android.graphics.BitmapFactory; 31import android.graphics.drawable.BitmapDrawable; 32import android.graphics.drawable.Drawable; 33import android.media.AudioManager; 34import android.media.MediaFile; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.Handler; 38import android.os.Message; 39import android.provider.MediaStore; 40import android.view.ContextMenu; 41import android.view.Menu; 42import android.view.MenuItem; 43import android.view.SubMenu; 44import android.view.View; 45import android.view.ViewGroup; 46import android.view.Window; 47import android.view.ContextMenu.ContextMenuInfo; 48import android.widget.Adapter; 49import android.widget.AlphabetIndexer; 50import android.widget.CursorAdapter; 51import android.widget.ImageView; 52import android.widget.ListAdapter; 53import android.widget.ListView; 54import android.widget.SectionIndexer; 55import android.widget.SimpleCursorAdapter; 56import android.widget.TextView; 57import android.widget.AdapterView.AdapterContextMenuInfo; 58 59import java.text.Collator; 60 61public class AlbumBrowserActivity extends ListActivity 62 implements View.OnCreateContextMenuListener, MusicUtils.Defs 63{ 64 private String mCurrentAlbumId; 65 private String mCurrentAlbumName; 66 private String mCurrentArtistNameForAlbum; 67 private AlbumListAdapter mAdapter; 68 private boolean mAdapterSent; 69 private final static int SEARCH = CHILD_MENU_BASE; 70 71 public AlbumBrowserActivity() 72 { 73 } 74 75 /** Called when the activity is first created. */ 76 @Override 77 public void onCreate(Bundle icicle) 78 { 79 if (icicle != null) { 80 mCurrentAlbumId = icicle.getString("selectedalbum"); 81 mArtistId = icicle.getString("artist"); 82 } else { 83 mArtistId = getIntent().getStringExtra("artist"); 84 } 85 super.onCreate(icicle); 86 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 87 setVolumeControlStream(AudioManager.STREAM_MUSIC); 88 MusicUtils.bindToService(this); 89 90 IntentFilter f = new IntentFilter(); 91 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 92 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 93 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 94 f.addDataScheme("file"); 95 registerReceiver(mScanListener, f); 96 97 setContentView(R.layout.media_picker_activity); 98 ListView lv = getListView(); 99 lv.setFastScrollEnabled(true); 100 lv.setOnCreateContextMenuListener(this); 101 lv.setTextFilterEnabled(true); 102 103 mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance(); 104 if (mAdapter == null) { 105 //Log.i("@@@", "starting query"); 106 mAdapter = new AlbumListAdapter( 107 getApplication(), 108 this, 109 R.layout.track_list_item, 110 mAlbumCursor, 111 new String[] {}, 112 new int[] {}); 113 setListAdapter(mAdapter); 114 setTitle(R.string.working_albums); 115 getAlbumCursor(mAdapter.getQueryHandler(), null); 116 } else { 117 mAdapter.setActivity(this); 118 setListAdapter(mAdapter); 119 mAlbumCursor = mAdapter.getCursor(); 120 if (mAlbumCursor != null) { 121 init(mAlbumCursor); 122 } else { 123 getAlbumCursor(mAdapter.getQueryHandler(), null); 124 } 125 } 126 } 127 128 @Override 129 public Object onRetainNonConfigurationInstance() { 130 mAdapterSent = true; 131 return mAdapter; 132 } 133 134 @Override 135 public void onSaveInstanceState(Bundle outcicle) { 136 // need to store the selected item so we don't lose it in case 137 // of an orientation switch. Otherwise we could lose it while 138 // in the middle of specifying a playlist to add the item to. 139 outcicle.putString("selectedalbum", mCurrentAlbumId); 140 outcicle.putString("artist", mArtistId); 141 super.onSaveInstanceState(outcicle); 142 } 143 144 @Override 145 public void onDestroy() { 146 MusicUtils.unbindFromService(this); 147 if (!mAdapterSent) { 148 Cursor c = mAdapter.getCursor(); 149 if (c != null) { 150 c.close(); 151 } 152 } 153 // Because we pass the adapter to the next activity, we need to make 154 // sure it doesn't keep a reference to this activity. We can do this 155 // by clearing its DatasetObservers, which setListAdapter(null) does. 156 setListAdapter(null); 157 mAdapter = null; 158 unregisterReceiver(mScanListener); 159 super.onDestroy(); 160 } 161 162 @Override 163 public void onResume() { 164 super.onResume(); 165 IntentFilter f = new IntentFilter(); 166 f.addAction(MediaPlaybackService.META_CHANGED); 167 f.addAction(MediaPlaybackService.QUEUE_CHANGED); 168 registerReceiver(mTrackListListener, f); 169 mTrackListListener.onReceive(null, null); 170 171 MusicUtils.setSpinnerState(this); 172 } 173 174 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { 175 @Override 176 public void onReceive(Context context, Intent intent) { 177 getListView().invalidateViews(); 178 } 179 }; 180 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 181 @Override 182 public void onReceive(Context context, Intent intent) { 183 MusicUtils.setSpinnerState(AlbumBrowserActivity.this); 184 mReScanHandler.sendEmptyMessage(0); 185 if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 186 MusicUtils.clearAlbumArtCache(); 187 } 188 } 189 }; 190 191 private Handler mReScanHandler = new Handler() { 192 @Override 193 public void handleMessage(Message msg) { 194 getAlbumCursor(mAdapter.getQueryHandler(), null); 195 } 196 }; 197 198 @Override 199 public void onPause() { 200 unregisterReceiver(mTrackListListener); 201 mReScanHandler.removeCallbacksAndMessages(null); 202 super.onPause(); 203 } 204 205 public void init(Cursor c) { 206 207 mAdapter.changeCursor(c); // also sets mAlbumCursor 208 209 if (mAlbumCursor == null) { 210 MusicUtils.displayDatabaseError(this); 211 closeContextMenu(); 212 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 213 return; 214 } 215 216 MusicUtils.hideDatabaseError(this); 217 setTitle(); 218 } 219 220 private void setTitle() { 221 CharSequence fancyName = ""; 222 if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) { 223 mAlbumCursor.moveToFirst(); 224 fancyName = mAlbumCursor.getString(3); 225 if (fancyName == null || fancyName.equals(MediaFile.UNKNOWN_STRING)) 226 fancyName = getText(R.string.unknown_artist_name); 227 } 228 229 if (mArtistId != null && fancyName != null) 230 setTitle(fancyName); 231 else 232 setTitle(R.string.albums_title); 233 } 234 235 @Override 236 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { 237 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); 238 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); 239 MusicUtils.makePlaylistMenu(this, sub); 240 menu.add(0, DELETE_ITEM, 0, R.string.delete_item); 241 menu.add(0, SEARCH, 0, R.string.search_title); 242 243 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn; 244 mAlbumCursor.moveToPosition(mi.position); 245 mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)); 246 mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 247 mCurrentArtistNameForAlbum = mAlbumCursor.getString( 248 mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)); 249 if (mCurrentAlbumName == null || mCurrentAlbumName.equals(MediaFile.UNKNOWN_STRING)) { 250 menu.setHeaderTitle(getString(R.string.unknown_album_name)); 251 } else { 252 menu.setHeaderTitle(mCurrentAlbumName); 253 } 254 } 255 256 @Override 257 public boolean onContextItemSelected(MenuItem item) { 258 switch (item.getItemId()) { 259 case PLAY_SELECTION: { 260 // play the selected album 261 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 262 MusicUtils.playAll(this, list, 0); 263 return true; 264 } 265 266 case QUEUE: { 267 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 268 MusicUtils.addToCurrentPlaylist(this, list); 269 return true; 270 } 271 272 case NEW_PLAYLIST: { 273 Intent intent = new Intent(); 274 intent.setClass(this, CreatePlaylist.class); 275 startActivityForResult(intent, NEW_PLAYLIST); 276 return true; 277 } 278 279 case PLAYLIST_SELECTED: { 280 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 281 int playlist = item.getIntent().getIntExtra("playlist", 0); 282 MusicUtils.addToPlaylist(this, list, playlist); 283 return true; 284 } 285 case DELETE_ITEM: { 286 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 287 String f = getString(R.string.delete_album_desc); 288 String desc = String.format(f, mCurrentAlbumName); 289 Bundle b = new Bundle(); 290 b.putString("description", desc); 291 b.putIntArray("items", list); 292 Intent intent = new Intent(); 293 intent.setClass(this, DeleteItems.class); 294 intent.putExtras(b); 295 startActivityForResult(intent, -1); 296 return true; 297 } 298 case SEARCH: 299 doSearch(); 300 return true; 301 302 } 303 return super.onContextItemSelected(item); 304 } 305 306 void doSearch() { 307 CharSequence title = null; 308 String query = null; 309 310 Intent i = new Intent(); 311 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); 312 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 313 314 title = mCurrentAlbumName; 315 query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName; 316 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); 317 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); 318 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE); 319 title = getString(R.string.mediasearch, title); 320 i.putExtra(SearchManager.QUERY, query); 321 322 startActivity(Intent.createChooser(i, title)); 323 } 324 325 @Override 326 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 327 switch (requestCode) { 328 case SCAN_DONE: 329 if (resultCode == RESULT_CANCELED) { 330 finish(); 331 } else { 332 getAlbumCursor(mAdapter.getQueryHandler(), null); 333 } 334 break; 335 336 case NEW_PLAYLIST: 337 if (resultCode == RESULT_OK) { 338 Uri uri = intent.getData(); 339 if (uri != null) { 340 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 341 MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment())); 342 } 343 } 344 break; 345 } 346 } 347 348 @Override 349 protected void onListItemClick(ListView l, View v, int position, long id) 350 { 351 Intent intent = new Intent(Intent.ACTION_PICK); 352 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 353 intent.putExtra("album", Long.valueOf(id).toString()); 354 intent.putExtra("artist", mArtistId); 355 startActivity(intent); 356 } 357 358 @Override 359 public boolean onCreateOptionsMenu(Menu menu) { 360 super.onCreateOptionsMenu(menu); 361 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library); 362 menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback); 363 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); 364 return true; 365 } 366 367 @Override 368 public boolean onPrepareOptionsMenu(Menu menu) { 369 menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded()); 370 return super.onPrepareOptionsMenu(menu); 371 } 372 373 @Override 374 public boolean onOptionsItemSelected(MenuItem item) { 375 Intent intent; 376 Cursor cursor; 377 switch (item.getItemId()) { 378 case GOTO_START: 379 intent = new Intent(); 380 intent.setClass(this, MusicBrowserActivity.class); 381 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 382 startActivity(intent); 383 return true; 384 385 case GOTO_PLAYBACK: 386 intent = new Intent("com.android.music.PLAYBACK_VIEWER"); 387 startActivity(intent); 388 return true; 389 390 case SHUFFLE_ALL: 391 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 392 new String [] { MediaStore.Audio.Media._ID}, 393 MediaStore.Audio.Media.IS_MUSIC + "=1", null, 394 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 395 if (cursor != null) { 396 MusicUtils.shuffleAll(this, cursor); 397 cursor.close(); 398 } 399 return true; 400 } 401 return super.onOptionsItemSelected(item); 402 } 403 404 private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) { 405 StringBuilder where = new StringBuilder(); 406 where.append(MediaStore.Audio.Albums.ALBUM + " != ''"); 407 408 // Add in the filtering constraints 409 String [] keywords = null; 410 if (filter != null) { 411 String [] searchWords = filter.split(" "); 412 keywords = new String[searchWords.length]; 413 Collator col = Collator.getInstance(); 414 col.setStrength(Collator.PRIMARY); 415 for (int i = 0; i < searchWords.length; i++) { 416 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%'; 417 } 418 for (int i = 0; i < searchWords.length; i++) { 419 where.append(" AND "); 420 where.append(MediaStore.Audio.Media.ARTIST_KEY + "||"); 421 where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?"); 422 } 423 } 424 425 String whereclause = where.toString(); 426 427 String[] cols = new String[] { 428 MediaStore.Audio.Albums._ID, 429 MediaStore.Audio.Albums.ARTIST, 430 MediaStore.Audio.Albums.ALBUM, 431 MediaStore.Audio.Albums.ALBUM_ART 432 }; 433 Cursor ret = null; 434 if (mArtistId != null) { 435 if (async != null) { 436 async.startQuery(0, null, 437 MediaStore.Audio.Artists.Albums.getContentUri("external", 438 Long.valueOf(mArtistId)), 439 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 440 } else { 441 ret = MusicUtils.query(this, 442 MediaStore.Audio.Artists.Albums.getContentUri("external", 443 Long.valueOf(mArtistId)), 444 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 445 } 446 } else { 447 if (async != null) { 448 async.startQuery(0, null, 449 MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, 450 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 451 } else { 452 ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, 453 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 454 } 455 } 456 return ret; 457 } 458 459 static class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer { 460 461 private final Drawable mNowPlayingOverlay; 462 private final BitmapDrawable mDefaultAlbumIcon; 463 private int mAlbumIdx; 464 private int mArtistIdx; 465 private int mAlbumArtIndex; 466 private final Resources mResources; 467 private final StringBuilder mStringBuilder = new StringBuilder(); 468 private final String mUnknownAlbum; 469 private final String mUnknownArtist; 470 private final String mAlbumSongSeparator; 471 private final Object[] mFormatArgs = new Object[1]; 472 private AlphabetIndexer mIndexer; 473 private AlbumBrowserActivity mActivity; 474 private AsyncQueryHandler mQueryHandler; 475 private String mConstraint = null; 476 private boolean mConstraintIsValid = false; 477 478 static class ViewHolder { 479 TextView line1; 480 TextView line2; 481 ImageView play_indicator; 482 ImageView icon; 483 } 484 485 class QueryHandler extends AsyncQueryHandler { 486 QueryHandler(ContentResolver res) { 487 super(res); 488 } 489 490 @Override 491 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 492 //Log.i("@@@", "query complete"); 493 mActivity.init(cursor); 494 } 495 } 496 497 AlbumListAdapter(Context context, AlbumBrowserActivity currentactivity, 498 int layout, Cursor cursor, String[] from, int[] to) { 499 super(context, layout, cursor, from, to); 500 501 mActivity = currentactivity; 502 mQueryHandler = new QueryHandler(context.getContentResolver()); 503 504 mUnknownAlbum = context.getString(R.string.unknown_album_name); 505 mUnknownArtist = context.getString(R.string.unknown_artist_name); 506 mAlbumSongSeparator = context.getString(R.string.albumsongseparator); 507 508 Resources r = context.getResources(); 509 mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list); 510 511 Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list); 512 mDefaultAlbumIcon = new BitmapDrawable(b); 513 // no filter or dither, it's a lot faster and we can't tell the difference 514 mDefaultAlbumIcon.setFilterBitmap(false); 515 mDefaultAlbumIcon.setDither(false); 516 getColumnIndices(cursor); 517 mResources = context.getResources(); 518 } 519 520 private void getColumnIndices(Cursor cursor) { 521 if (cursor != null) { 522 mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM); 523 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST); 524 mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART); 525 526 if (mIndexer != null) { 527 mIndexer.setCursor(cursor); 528 } else { 529 mIndexer = new MusicAlphabetIndexer(cursor, mAlbumIdx, mResources.getString( 530 com.android.internal.R.string.fast_scroll_alphabet)); 531 } 532 } 533 } 534 535 public void setActivity(AlbumBrowserActivity newactivity) { 536 mActivity = newactivity; 537 } 538 539 public AsyncQueryHandler getQueryHandler() { 540 return mQueryHandler; 541 } 542 543 @Override 544 public View newView(Context context, Cursor cursor, ViewGroup parent) { 545 View v = super.newView(context, cursor, parent); 546 ViewHolder vh = new ViewHolder(); 547 vh.line1 = (TextView) v.findViewById(R.id.line1); 548 vh.line2 = (TextView) v.findViewById(R.id.line2); 549 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 550 vh.icon = (ImageView) v.findViewById(R.id.icon); 551 vh.icon.setBackgroundDrawable(mDefaultAlbumIcon); 552 vh.icon.setPadding(0, 0, 1, 0); 553 v.setTag(vh); 554 return v; 555 } 556 557 @Override 558 public void bindView(View view, Context context, Cursor cursor) { 559 560 ViewHolder vh = (ViewHolder) view.getTag(); 561 562 String name = cursor.getString(mAlbumIdx); 563 String displayname = name; 564 boolean unknown = name == null || name.equals(MediaFile.UNKNOWN_STRING); 565 if (unknown) { 566 displayname = mUnknownAlbum; 567 } 568 vh.line1.setText(displayname); 569 570 name = cursor.getString(mArtistIdx); 571 displayname = name; 572 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 573 displayname = mUnknownArtist; 574 } 575 vh.line2.setText(displayname); 576 577 ImageView iv = vh.icon; 578 // We don't actually need the path to the thumbnail file, 579 // we just use it to see if there is album art or not 580 String art = cursor.getString(mAlbumArtIndex); 581 if (unknown || art == null || art.length() == 0) { 582 iv.setImageDrawable(null); 583 } else { 584 int artIndex = cursor.getInt(0); 585 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon); 586 iv.setImageDrawable(d); 587 } 588 589 int currentalbumid = MusicUtils.getCurrentAlbumId(); 590 int aid = cursor.getInt(0); 591 iv = vh.play_indicator; 592 if (currentalbumid == aid) { 593 iv.setImageDrawable(mNowPlayingOverlay); 594 } else { 595 iv.setImageDrawable(null); 596 } 597 } 598 599 @Override 600 public void changeCursor(Cursor cursor) { 601 if (cursor != mActivity.mAlbumCursor) { 602 mActivity.mAlbumCursor = cursor; 603 getColumnIndices(cursor); 604 super.changeCursor(cursor); 605 } 606 } 607 608 @Override 609 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 610 String s = constraint.toString(); 611 if (mConstraintIsValid && ( 612 (s == null && mConstraint == null) || 613 (s != null && s.equals(mConstraint)))) { 614 return getCursor(); 615 } 616 Cursor c = mActivity.getAlbumCursor(null, s); 617 mConstraint = s; 618 mConstraintIsValid = true; 619 return c; 620 } 621 622 public Object[] getSections() { 623 return mIndexer.getSections(); 624 } 625 626 public int getPositionForSection(int section) { 627 return mIndexer.getPositionForSection(section); 628 } 629 630 public int getSectionForPosition(int position) { 631 return 0; 632 } 633 } 634 635 private Cursor mAlbumCursor; 636 private String mArtistId; 637} 638 639