AlbumBrowserActivity.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 java.text.Collator; 20 21import android.app.ListActivity; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.BroadcastReceiver; 26import android.content.res.Resources; 27import android.database.Cursor; 28import android.media.AudioManager; 29import android.media.MediaFile; 30import android.net.Uri; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.Message; 34import android.provider.MediaStore; 35import android.view.ContextMenu; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.SubMenu; 39import android.view.View; 40import android.view.ViewGroup; 41import android.view.Window; 42import android.view.ContextMenu.ContextMenuInfo; 43import android.widget.AdapterView.AdapterContextMenuInfo; 44import android.widget.ImageView; 45import android.widget.ListView; 46import android.widget.SimpleCursorAdapter; 47import android.widget.TextView; 48import android.graphics.Bitmap; 49import android.graphics.BitmapFactory; 50import android.graphics.drawable.BitmapDrawable; 51import android.graphics.drawable.Drawable; 52 53public class AlbumBrowserActivity extends ListActivity 54 implements View.OnCreateContextMenuListener, MusicUtils.Defs 55{ 56 private String mCurrentAlbumId; 57 private String mCurrentAlbumName; 58 59 public AlbumBrowserActivity() 60 { 61 } 62 63 /** Called when the activity is first created. */ 64 @Override 65 public void onCreate(Bundle icicle) 66 { 67 if (icicle != null) { 68 mCurrentAlbumId = icicle.getString("selectedalbum"); 69 mArtistId = icicle.getString("artist"); 70 } else { 71 mArtistId = getIntent().getStringExtra("artist"); 72 } 73 super.onCreate(icicle); 74 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 75 setVolumeControlStream(AudioManager.STREAM_MUSIC); 76 MusicUtils.bindToService(this); 77 78 IntentFilter f = new IntentFilter(); 79 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 80 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 81 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 82 f.addDataScheme("file"); 83 registerReceiver(mScanListener, f); 84 85 init(); 86 } 87 88 @Override 89 public void onSaveInstanceState(Bundle outcicle) { 90 // need to store the selected item so we don't lose it in case 91 // of an orientation switch. Otherwise we could lose it while 92 // in the middle of specifying a playlist to add the item to. 93 outcicle.putString("selectedalbum", mCurrentAlbumId); 94 outcicle.putString("artist", mArtistId); 95 super.onSaveInstanceState(outcicle); 96 } 97 98 @Override 99 public void onDestroy() { 100 MusicUtils.unbindFromService(this); 101 if (mAlbumCursor != null) { 102 mAlbumCursor.close(); 103 } 104 unregisterReceiver(mScanListener); 105 super.onDestroy(); 106 } 107 108 @Override 109 public void onResume() { 110 super.onResume(); 111 IntentFilter f = new IntentFilter(); 112 f.addAction(MediaPlaybackService.META_CHANGED); 113 f.addAction(MediaPlaybackService.QUEUE_CHANGED); 114 registerReceiver(mTrackListListener, f); 115 mTrackListListener.onReceive(null, null); 116 117 MusicUtils.setSpinnerState(this); 118 } 119 120 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { 121 @Override 122 public void onReceive(Context context, Intent intent) { 123 getListView().invalidateViews(); 124 } 125 }; 126 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 127 @Override 128 public void onReceive(Context context, Intent intent) { 129 MusicUtils.setSpinnerState(AlbumBrowserActivity.this); 130 mReScanHandler.sendEmptyMessage(0); 131 if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 132 MusicUtils.clearAlbumArtCache(); 133 } 134 } 135 }; 136 137 private Handler mReScanHandler = new Handler() { 138 public void handleMessage(Message msg) { 139 init(); 140 if (mAlbumCursor == null) { 141 sendEmptyMessageDelayed(0, 1000); 142 } 143 } 144 }; 145 146 @Override 147 public void onPause() { 148 unregisterReceiver(mTrackListListener); 149 mReScanHandler.removeCallbacksAndMessages(null); 150 super.onPause(); 151 } 152 153 public void init() { 154 155 mAlbumCursor = getAlbumCursor(null); 156 157 /* The UI spec does not call for icons in the title bar 158 if (mArtistId != null) { 159 requestWindowFeature(Window.FEATURE_LEFT_ICON); 160 } 161 */ 162 setContentView(R.layout.media_picker_activity); 163 /* The UI spec does not call for icons in the title bar 164 if (mArtistId != null) { 165 // this call needs to happen after setContentView 166 setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.person_title_icon); 167 } 168 */ 169 170 if (mAlbumCursor == null) { 171 MusicUtils.displayDatabaseError(this); 172 return; 173 } 174 175 CharSequence fancyName = ""; 176 if (mAlbumCursor.getCount() > 0) { 177 mAlbumCursor.moveToFirst(); 178 fancyName = mAlbumCursor.getString(3); 179 if (MediaFile.UNKNOWN_STRING.equals(fancyName)) 180 fancyName = getText(R.string.unknown_artist_name); 181 182 if (mArtistId != null && fancyName != null) 183 setTitle(fancyName); 184 else 185 setTitle(R.string.albums_title); 186 } else { 187 setTitle(R.string.no_albums_title); 188 } 189 190 // Map Cursor columns to views defined in media_list_item.xml 191 AlbumListAdapter adapter = new AlbumListAdapter( 192 this, 193 R.layout.track_list_item, 194 mAlbumCursor, 195 new String[] {}, 196 new int[] {}); 197 198 setListAdapter(adapter); 199 ListView lv = getListView(); 200 lv.setOnCreateContextMenuListener(this); 201 lv.setTextFilterEnabled(true); 202 } 203 204 @Override 205 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { 206 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); 207 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); 208 MusicUtils.makePlaylistMenu(this, sub); 209 menu.add(0, DELETE_ITEM, 0, R.string.delete_item); 210 211 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn; 212 mAlbumCursor.moveToPosition(mi.position); 213 mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)); 214 mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 215 menu.setHeaderTitle(mCurrentAlbumName); 216 } 217 218 @Override 219 public boolean onContextItemSelected(MenuItem item) { 220 switch (item.getItemId()) { 221 case PLAY_SELECTION: { 222 // play the selected album 223 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 224 MusicUtils.playAll(this, list, 0); 225 return true; 226 } 227 228 case QUEUE: { 229 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 230 MusicUtils.addToCurrentPlaylist(this, list); 231 return true; 232 } 233 234 case NEW_PLAYLIST: { 235 Intent intent = new Intent(); 236 intent.setClass(this, CreatePlaylist.class); 237 startActivityForResult(intent, NEW_PLAYLIST); 238 return true; 239 } 240 241 case PLAYLIST_SELECTED: { 242 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 243 int playlist = item.getIntent().getIntExtra("playlist", 0); 244 MusicUtils.addToPlaylist(this, list, playlist); 245 return true; 246 } 247 case DELETE_ITEM: { 248 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 249 String f = getString(R.string.delete_album_desc); 250 String desc = String.format(f, mCurrentAlbumName); 251 Bundle b = new Bundle(); 252 b.putString("description", desc); 253 b.putIntArray("items", list); 254 Intent intent = new Intent(); 255 intent.setClass(this, DeleteItems.class); 256 intent.putExtras(b); 257 startActivityForResult(intent, -1); 258 return true; 259 } 260 261 } 262 return super.onContextItemSelected(item); 263 } 264 265 @Override 266 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 267 switch (requestCode) { 268 case SCAN_DONE: 269 if (resultCode == RESULT_CANCELED) { 270 finish(); 271 } else { 272 init(); 273 } 274 break; 275 276 case NEW_PLAYLIST: 277 if (resultCode == RESULT_OK) { 278 Uri uri = Uri.parse(intent.getAction()); 279 if (uri != null) { 280 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId)); 281 MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment())); 282 } 283 } 284 break; 285 } 286 } 287 288 @Override 289 protected void onListItemClick(ListView l, View v, int position, long id) 290 { 291 if (mHasHeader) { 292 position --; 293 } 294 Intent intent = new Intent(Intent.ACTION_PICK); 295 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 296 intent.putExtra("album", Long.valueOf(id).toString()); 297 intent.putExtra("artist", mArtistId); 298 startActivity(intent); 299 } 300 301 @Override 302 public boolean onCreateOptionsMenu(Menu menu) { 303 super.onCreateOptionsMenu(menu); 304 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library); 305 menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback); 306 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); 307 return true; 308 } 309 310 @Override 311 public boolean onPrepareOptionsMenu(Menu menu) { 312 menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded()); 313 return super.onPrepareOptionsMenu(menu); 314 } 315 316 @Override 317 public boolean onOptionsItemSelected(MenuItem item) { 318 Intent intent; 319 Cursor cursor; 320 switch (item.getItemId()) { 321 case GOTO_START: 322 intent = new Intent(); 323 intent.setClass(this, MusicBrowserActivity.class); 324 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 325 startActivity(intent); 326 return true; 327 328 case GOTO_PLAYBACK: 329 intent = new Intent("com.android.music.PLAYBACK_VIEWER"); 330 startActivity(intent); 331 return true; 332 333 case SHUFFLE_ALL: 334 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 335 new String [] { MediaStore.Audio.Media._ID}, 336 MediaStore.Audio.Media.IS_MUSIC + "=1", null, 337 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 338 if (cursor != null) { 339 MusicUtils.shuffleAll(this, cursor); 340 cursor.close(); 341 } 342 return true; 343 } 344 return super.onOptionsItemSelected(item); 345 } 346 347 private Cursor getAlbumCursor(String filterstring) { 348 StringBuilder where = new StringBuilder(); 349 where.append(MediaStore.Audio.Albums.ALBUM + " != ''"); 350 351 // Add in the filtering constraints 352 String [] keywords = null; 353 if (filterstring != null) { 354 String [] searchWords = filterstring.split(" "); 355 keywords = new String[searchWords.length]; 356 Collator col = Collator.getInstance(); 357 col.setStrength(Collator.PRIMARY); 358 for (int i = 0; i < searchWords.length; i++) { 359 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%'; 360 } 361 for (int i = 0; i < searchWords.length; i++) { 362 where.append(" AND "); 363 where.append(MediaStore.Audio.Media.ARTIST_KEY + "||"); 364 where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?"); 365 } 366 } 367 368 String whereclause = where.toString(); 369 370 String[] cols = new String[] { 371 MediaStore.Audio.Albums._ID, 372 MediaStore.Audio.Albums.ALBUM, 373 MediaStore.Audio.Albums.ALBUM_KEY, 374 MediaStore.Audio.Albums.ARTIST, 375 MediaStore.Audio.Albums.NUMBER_OF_SONGS, 376 MediaStore.Audio.Albums.ALBUM_ART 377 }; 378 Cursor ret; 379 if (mArtistId != null) { 380 ret = MusicUtils.query(this, 381 MediaStore.Audio.Artists.Albums.getContentUri("external", Long.valueOf(mArtistId)), 382 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 383 } else { 384 ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, 385 cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 386 } 387 return ret; 388 } 389 390 class AlbumListAdapter extends SimpleCursorAdapter { 391 392 private final Drawable mNowPlayingOverlay; 393 private final BitmapDrawable mDefaultAlbumIcon; 394 private final int mAlbumIdx; 395 private final int mArtistIdx; 396 private final int mNumSongsIdx; 397 private final int mAlbumArtIndex; 398 private final Resources mResources; 399 private final StringBuilder mStringBuilder = new StringBuilder(); 400 private final String mUnknownAlbum; 401 private final String mUnknownArtist; 402 private final String mAlbumSongSeparator; 403 private final Object[] mFormatArgs = new Object[1]; 404 405 class ViewHolder { 406 TextView line1; 407 TextView line2; 408 TextView duration; 409 ImageView play_indicator; 410 ImageView icon; 411 } 412 413 AlbumListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) { 414 super(context, layout, cursor, from, to); 415 416 mUnknownAlbum = context.getString(R.string.unknown_album_name); 417 mUnknownArtist = context.getString(R.string.unknown_artist_name); 418 mAlbumSongSeparator = context.getString(R.string.albumsongseparator); 419 420 Resources r = getResources(); 421 mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list); 422 423 Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list); 424 mDefaultAlbumIcon = new BitmapDrawable(b); 425 // no filter or dither, it's a lot faster and we can't tell the difference 426 mDefaultAlbumIcon.setFilterBitmap(false); 427 mDefaultAlbumIcon.setDither(false); 428 mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM); 429 mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST); 430 mNumSongsIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS); 431 mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART); 432 433 mResources = context.getResources(); 434 } 435 436 @Override 437 public View newView(Context context, Cursor cursor, ViewGroup parent) { 438 View v = super.newView(context, cursor, parent); 439 ViewHolder vh = new ViewHolder(); 440 vh.line1 = (TextView) v.findViewById(R.id.line1); 441 vh.line2 = (TextView) v.findViewById(R.id.line2); 442 vh.duration = (TextView) v.findViewById(R.id.duration); 443 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 444 vh.icon = (ImageView) v.findViewById(R.id.icon); 445 vh.icon.setBackgroundDrawable(mDefaultAlbumIcon); 446 vh.icon.setPadding(1, 1, 1, 1); 447 v.setTag(vh); 448 return v; 449 } 450 451 @Override 452 public void bindView(View view, Context context, Cursor cursor) { 453 454 ViewHolder vh = (ViewHolder) view.getTag(); 455 456 String name = cursor.getString(mAlbumIdx); 457 String displayname = name; 458 if (MediaFile.UNKNOWN_STRING.equals(name)) { 459 displayname = mUnknownAlbum; 460 } 461 vh.line1.setText(displayname); 462 463 name = cursor.getString(mArtistIdx); 464 displayname = name; 465 if (MediaFile.UNKNOWN_STRING.equals(name)) { 466 displayname = mUnknownArtist; 467 } 468 StringBuilder builder = mStringBuilder; 469 builder.delete(0, builder.length()); 470 builder.append(displayname); 471 builder.append(mAlbumSongSeparator); 472 473 int numsongs = cursor.getInt(mNumSongsIdx); 474 if (numsongs == 1) { 475 builder.append(context.getString(R.string.onesong)); 476 } else { 477 final Object[] args = mFormatArgs; 478 args[0] = numsongs; 479 builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args)); 480 } 481 vh.line2.setText(builder.toString()); 482 483 ImageView iv = vh.icon; 484 // We don't actually need the path to the thumbnail file, 485 // we just use it to see if there is album art or not 486 String art = cursor.getString(mAlbumArtIndex); 487 if (art == null || art.length() == 0) { 488 iv.setImageDrawable(null); 489 } else { 490 int artIndex = cursor.getInt(0); 491 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon); 492 iv.setImageDrawable(d); 493 } 494 495 int currentalbumid = MusicUtils.getCurrentAlbumId(); 496 int aid = cursor.getInt(0); 497 iv = vh.play_indicator; 498 if (currentalbumid == aid) { 499 iv.setImageDrawable(mNowPlayingOverlay); 500 } else { 501 iv.setImageDrawable(null); 502 } 503 } 504 @Override 505 public void changeCursor(Cursor cursor) { 506 super.changeCursor(cursor); 507 mAlbumCursor = cursor; 508 } 509 @Override 510 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 511 return getAlbumCursor(constraint.toString()); 512 } 513 } 514 515 private Cursor mAlbumCursor; 516 private String mArtistId; 517 private boolean mHasHeader = false; 518} 519 520