ArtistAlbumBrowserActivity.java revision c234eed2feb83baa478c6fde1c537227b92ce674
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 com.android.music.MusicUtils.ServiceToken; 20import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler; 21 22import android.app.ExpandableListActivity; 23import android.app.SearchManager; 24import android.content.AsyncQueryHandler; 25import android.content.BroadcastReceiver; 26import android.content.ComponentName; 27import android.content.ContentResolver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.ServiceConnection; 32import android.content.res.Resources; 33import android.database.Cursor; 34import android.database.CursorWrapper; 35import android.graphics.drawable.BitmapDrawable; 36import android.graphics.drawable.Drawable; 37import android.media.AudioManager; 38import android.net.Uri; 39import android.os.Bundle; 40import android.os.Handler; 41import android.os.IBinder; 42import android.os.Message; 43import android.os.Parcel; 44import android.os.Parcelable; 45import android.provider.MediaStore; 46import android.util.Log; 47import android.util.SparseArray; 48import android.view.ContextMenu; 49import android.view.Menu; 50import android.view.MenuItem; 51import android.view.SubMenu; 52import android.view.View; 53import android.view.ViewGroup; 54import android.view.Window; 55import android.view.ContextMenu.ContextMenuInfo; 56import android.widget.ExpandableListView; 57import android.widget.ImageView; 58import android.widget.SectionIndexer; 59import android.widget.SimpleCursorTreeAdapter; 60import android.widget.TextView; 61import android.widget.ExpandableListView.ExpandableListContextMenuInfo; 62 63import java.text.Collator; 64 65 66public class ArtistAlbumBrowserActivity extends ExpandableListActivity 67 implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection 68{ 69 private String mCurrentArtistId; 70 private String mCurrentArtistName; 71 private String mCurrentAlbumId; 72 private String mCurrentAlbumName; 73 private String mCurrentArtistNameForAlbum; 74 boolean mIsUnknownArtist; 75 boolean mIsUnknownAlbum; 76 private ArtistAlbumListAdapter mAdapter; 77 private boolean mAdapterSent; 78 private final static int SEARCH = CHILD_MENU_BASE; 79 private static int mLastListPosCourse = -1; 80 private static int mLastListPosFine = -1; 81 private ServiceToken mToken; 82 83 /** Called when the activity is first created. */ 84 @Override 85 public void onCreate(Bundle icicle) { 86 super.onCreate(icicle); 87 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 88 requestWindowFeature(Window.FEATURE_NO_TITLE); 89 setVolumeControlStream(AudioManager.STREAM_MUSIC); 90 if (icicle != null) { 91 mCurrentAlbumId = icicle.getString("selectedalbum"); 92 mCurrentAlbumName = icicle.getString("selectedalbumname"); 93 mCurrentArtistId = icicle.getString("selectedartist"); 94 mCurrentArtistName = icicle.getString("selectedartistname"); 95 } 96 mToken = MusicUtils.bindToService(this, this); 97 98 IntentFilter f = new IntentFilter(); 99 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 100 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 101 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 102 f.addDataScheme("file"); 103 registerReceiver(mScanListener, f); 104 105 setContentView(R.layout.media_picker_activity_expanding); 106 MusicUtils.updateButtonBar(this, R.id.artisttab); 107 ExpandableListView lv = getExpandableListView(); 108 lv.setOnCreateContextMenuListener(this); 109 lv.setTextFilterEnabled(true); 110 111 mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance(); 112 if (mAdapter == null) { 113 //Log.i("@@@", "starting query"); 114 mAdapter = new ArtistAlbumListAdapter( 115 getApplication(), 116 this, 117 null, // cursor 118 R.layout.track_list_item_group, 119 new String[] {}, 120 new int[] {}, 121 R.layout.track_list_item_child, 122 new String[] {}, 123 new int[] {}); 124 setListAdapter(mAdapter); 125 setTitle(R.string.working_artists); 126 getArtistCursor(mAdapter.getQueryHandler(), null); 127 } else { 128 mAdapter.setActivity(this); 129 setListAdapter(mAdapter); 130 mArtistCursor = mAdapter.getCursor(); 131 if (mArtistCursor != null) { 132 init(mArtistCursor); 133 } else { 134 getArtistCursor(mAdapter.getQueryHandler(), null); 135 } 136 } 137 } 138 139 @Override 140 public Object onRetainNonConfigurationInstance() { 141 mAdapterSent = true; 142 return mAdapter; 143 } 144 145 @Override 146 public void onSaveInstanceState(Bundle outcicle) { 147 // need to store the selected item so we don't lose it in case 148 // of an orientation switch. Otherwise we could lose it while 149 // in the middle of specifying a playlist to add the item to. 150 outcicle.putString("selectedalbum", mCurrentAlbumId); 151 outcicle.putString("selectedalbumname", mCurrentAlbumName); 152 outcicle.putString("selectedartist", mCurrentArtistId); 153 outcicle.putString("selectedartistname", mCurrentArtistName); 154 super.onSaveInstanceState(outcicle); 155 } 156 157 @Override 158 public void onDestroy() { 159 ExpandableListView lv = getExpandableListView(); 160 if (lv != null) { 161 mLastListPosCourse = lv.getFirstVisiblePosition(); 162 View cv = lv.getChildAt(0); 163 if (cv != null) { 164 mLastListPosFine = cv.getTop(); 165 } 166 } 167 168 MusicUtils.unbindFromService(mToken); 169 // If we have an adapter and didn't send it off to another activity yet, we should 170 // close its cursor, which we do by assigning a null cursor to it. Doing this 171 // instead of closing the cursor directly keeps the framework from accessing 172 // the closed cursor later. 173 if (!mAdapterSent && mAdapter != null) { 174 mAdapter.changeCursor(null); 175 } 176 // Because we pass the adapter to the next activity, we need to make 177 // sure it doesn't keep a reference to this activity. We can do this 178 // by clearing its DatasetObservers, which setListAdapter(null) does. 179 setListAdapter(null); 180 mAdapter = null; 181 unregisterReceiver(mScanListener); 182 setListAdapter(null); 183 super.onDestroy(); 184 } 185 186 @Override 187 public void onResume() { 188 super.onResume(); 189 IntentFilter f = new IntentFilter(); 190 f.addAction(MediaPlaybackService.META_CHANGED); 191 f.addAction(MediaPlaybackService.QUEUE_CHANGED); 192 registerReceiver(mTrackListListener, f); 193 mTrackListListener.onReceive(null, null); 194 195 MusicUtils.setSpinnerState(this); 196 } 197 198 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { 199 @Override 200 public void onReceive(Context context, Intent intent) { 201 getExpandableListView().invalidateViews(); 202 MusicUtils.updateNowPlaying(ArtistAlbumBrowserActivity.this); 203 } 204 }; 205 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 206 @Override 207 public void onReceive(Context context, Intent intent) { 208 MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this); 209 mReScanHandler.sendEmptyMessage(0); 210 if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 211 MusicUtils.clearAlbumArtCache(); 212 } 213 } 214 }; 215 216 private Handler mReScanHandler = new Handler() { 217 @Override 218 public void handleMessage(Message msg) { 219 if (mAdapter != null) { 220 getArtistCursor(mAdapter.getQueryHandler(), null); 221 } 222 } 223 }; 224 225 @Override 226 public void onPause() { 227 unregisterReceiver(mTrackListListener); 228 mReScanHandler.removeCallbacksAndMessages(null); 229 super.onPause(); 230 } 231 232 public void init(Cursor c) { 233 234 if (mAdapter == null) { 235 return; 236 } 237 mAdapter.changeCursor(c); // also sets mArtistCursor 238 239 if (mArtistCursor == null) { 240 MusicUtils.displayDatabaseError(this); 241 closeContextMenu(); 242 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 243 return; 244 } 245 246 // restore previous position 247 if (mLastListPosCourse >= 0) { 248 ExpandableListView elv = getExpandableListView(); 249 elv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine); 250 mLastListPosCourse = -1; 251 } 252 253 MusicUtils.hideDatabaseError(this); 254 MusicUtils.updateButtonBar(this, R.id.artisttab); 255 setTitle(); 256 } 257 258 private void setTitle() { 259 setTitle(R.string.artists_title); 260 } 261 262 @Override 263 public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { 264 265 mCurrentAlbumId = Long.valueOf(id).toString(); 266 267 Intent intent = new Intent(Intent.ACTION_PICK); 268 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 269 intent.putExtra("album", mCurrentAlbumId); 270 Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition); 271 String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM)); 272 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) { 273 // unknown album, so we should include the artist ID to limit the songs to songs only by that artist 274 mArtistCursor.moveToPosition(groupPosition); 275 mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID)); 276 intent.putExtra("artist", mCurrentArtistId); 277 } 278 startActivity(intent); 279 return true; 280 } 281 282 @Override 283 public boolean onCreateOptionsMenu(Menu menu) { 284 super.onCreateOptionsMenu(menu); 285 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu() 286 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); 287 return true; 288 } 289 290 @Override 291 public boolean onPrepareOptionsMenu(Menu menu) { 292 MusicUtils.setPartyShuffleMenuIcon(menu); 293 return super.onPrepareOptionsMenu(menu); 294 } 295 296 @Override 297 public boolean onOptionsItemSelected(MenuItem item) { 298 Intent intent; 299 Cursor cursor; 300 switch (item.getItemId()) { 301 case PARTY_SHUFFLE: 302 MusicUtils.togglePartyShuffle(); 303 break; 304 305 case SHUFFLE_ALL: 306 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 307 new String [] { MediaStore.Audio.Media._ID}, 308 MediaStore.Audio.Media.IS_MUSIC + "=1", null, 309 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 310 if (cursor != null) { 311 MusicUtils.shuffleAll(this, cursor); 312 cursor.close(); 313 } 314 return true; 315 } 316 return super.onOptionsItemSelected(item); 317 } 318 319 @Override 320 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { 321 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); 322 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); 323 MusicUtils.makePlaylistMenu(this, sub); 324 menu.add(0, DELETE_ITEM, 0, R.string.delete_item); 325 326 ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn; 327 328 int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition); 329 int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition); 330 int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition); 331 if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { 332 if (gpos == -1) { 333 // this shouldn't happen 334 Log.d("Artist/Album", "no group"); 335 return; 336 } 337 gpos = gpos - getExpandableListView().getHeaderViewsCount(); 338 mArtistCursor.moveToPosition(gpos); 339 mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)); 340 mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 341 mCurrentAlbumId = null; 342 mIsUnknownArtist = mCurrentArtistName == null || 343 mCurrentArtistName.equals(MediaStore.UNKNOWN_STRING); 344 mIsUnknownAlbum = true; 345 if (mIsUnknownArtist) { 346 menu.setHeaderTitle(getString(R.string.unknown_artist_name)); 347 } else { 348 menu.setHeaderTitle(mCurrentArtistName); 349 menu.add(0, SEARCH, 0, R.string.search_title); 350 } 351 return; 352 } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { 353 if (cpos == -1) { 354 // this shouldn't happen 355 Log.d("Artist/Album", "no child"); 356 return; 357 } 358 Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos); 359 c.moveToPosition(cpos); 360 mCurrentArtistId = null; 361 mCurrentAlbumId = Long.valueOf(mi.id).toString(); 362 mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 363 gpos = gpos - getExpandableListView().getHeaderViewsCount(); 364 mArtistCursor.moveToPosition(gpos); 365 mCurrentArtistNameForAlbum = mArtistCursor.getString( 366 mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 367 mIsUnknownArtist = mCurrentArtistNameForAlbum == null || 368 mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING); 369 mIsUnknownAlbum = mCurrentAlbumName == null || 370 mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING); 371 if (mIsUnknownAlbum) { 372 menu.setHeaderTitle(getString(R.string.unknown_album_name)); 373 } else { 374 menu.setHeaderTitle(mCurrentAlbumName); 375 } 376 if (!mIsUnknownAlbum || !mIsUnknownArtist) { 377 menu.add(0, SEARCH, 0, R.string.search_title); 378 } 379 } 380 } 381 382 @Override 383 public boolean onContextItemSelected(MenuItem item) { 384 switch (item.getItemId()) { 385 case PLAY_SELECTION: { 386 // play everything by the selected artist 387 long [] list = 388 mCurrentArtistId != null ? 389 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 390 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 391 392 MusicUtils.playAll(this, list, 0); 393 return true; 394 } 395 396 case QUEUE: { 397 long [] list = 398 mCurrentArtistId != null ? 399 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 400 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 401 MusicUtils.addToCurrentPlaylist(this, list); 402 return true; 403 } 404 405 case NEW_PLAYLIST: { 406 Intent intent = new Intent(); 407 intent.setClass(this, CreatePlaylist.class); 408 startActivityForResult(intent, NEW_PLAYLIST); 409 return true; 410 } 411 412 case PLAYLIST_SELECTED: { 413 long [] list = 414 mCurrentArtistId != null ? 415 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 416 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 417 long playlist = item.getIntent().getLongExtra("playlist", 0); 418 MusicUtils.addToPlaylist(this, list, playlist); 419 return true; 420 } 421 422 case DELETE_ITEM: { 423 long [] list; 424 String desc; 425 if (mCurrentArtistId != null) { 426 list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)); 427 String f = getString(R.string.delete_artist_desc); 428 desc = String.format(f, mCurrentArtistName); 429 } else { 430 list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 431 String f = getString(R.string.delete_album_desc); 432 desc = String.format(f, mCurrentAlbumName); 433 } 434 Bundle b = new Bundle(); 435 b.putString("description", desc); 436 b.putLongArray("items", list); 437 Intent intent = new Intent(); 438 intent.setClass(this, DeleteItems.class); 439 intent.putExtras(b); 440 startActivityForResult(intent, -1); 441 return true; 442 } 443 444 case SEARCH: 445 doSearch(); 446 return true; 447 } 448 return super.onContextItemSelected(item); 449 } 450 451 void doSearch() { 452 CharSequence title = null; 453 String query = null; 454 455 Intent i = new Intent(); 456 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); 457 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 458 459 if (mCurrentArtistId != null) { 460 title = mCurrentArtistName; 461 query = mCurrentArtistName; 462 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName); 463 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE); 464 } else { 465 if (mIsUnknownAlbum) { 466 title = query = mCurrentArtistNameForAlbum; 467 } else { 468 title = query = mCurrentAlbumName; 469 if (!mIsUnknownArtist) { 470 query = query + " " + mCurrentArtistNameForAlbum; 471 } 472 } 473 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); 474 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); 475 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE); 476 } 477 title = getString(R.string.mediasearch, title); 478 i.putExtra(SearchManager.QUERY, query); 479 480 startActivity(Intent.createChooser(i, title)); 481 } 482 483 @Override 484 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 485 switch (requestCode) { 486 case SCAN_DONE: 487 if (resultCode == RESULT_CANCELED) { 488 finish(); 489 } else { 490 getArtistCursor(mAdapter.getQueryHandler(), null); 491 } 492 break; 493 494 case NEW_PLAYLIST: 495 if (resultCode == RESULT_OK) { 496 Uri uri = intent.getData(); 497 if (uri != null) { 498 long [] list = null; 499 if (mCurrentArtistId != null) { 500 list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)); 501 } else if (mCurrentAlbumId != null) { 502 list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 503 } 504 MusicUtils.addToPlaylist(this, list, Long.parseLong(uri.getLastPathSegment())); 505 } 506 } 507 break; 508 } 509 } 510 511 private Cursor getArtistCursor(AsyncQueryHandler async, String filter) { 512 513 StringBuilder where = new StringBuilder(); 514 where.append(MediaStore.Audio.Artists.ARTIST + " != ''"); 515 516 // Add in the filtering constraints 517 String [] keywords = null; 518 if (filter != null) { 519 String [] searchWords = filter.split(" "); 520 keywords = new String[searchWords.length]; 521 Collator col = Collator.getInstance(); 522 col.setStrength(Collator.PRIMARY); 523 for (int i = 0; i < searchWords.length; i++) { 524 String key = MediaStore.Audio.keyFor(searchWords[i]); 525 key = key.replace("\\", "\\\\"); 526 key = key.replace("%", "\\%"); 527 key = key.replace("_", "\\_"); 528 keywords[i] = '%' + key + '%'; 529 } 530 for (int i = 0; i < searchWords.length; i++) { 531 where.append(" AND "); 532 where.append(MediaStore.Audio.Media.ARTIST_KEY + " LIKE ? ESCAPE '\\'"); 533 } 534 } 535 536 String whereclause = where.toString(); 537 String[] cols = new String[] { 538 MediaStore.Audio.Artists._ID, 539 MediaStore.Audio.Artists.ARTIST, 540 MediaStore.Audio.Artists.NUMBER_OF_ALBUMS, 541 MediaStore.Audio.Artists.NUMBER_OF_TRACKS 542 }; 543 Cursor ret = null; 544 if (async != null) { 545 async.startQuery(0, null, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 546 cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY); 547 } else { 548 ret = MusicUtils.query(this, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 549 cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY); 550 } 551 return ret; 552 } 553 554 static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer { 555 556 private final Drawable mNowPlayingOverlay; 557 private final BitmapDrawable mDefaultAlbumIcon; 558 private int mGroupArtistIdIdx; 559 private int mGroupArtistIdx; 560 private int mGroupAlbumIdx; 561 private int mGroupSongIdx; 562 private final Context mContext; 563 private final Resources mResources; 564 private final String mAlbumSongSeparator; 565 private final String mUnknownAlbum; 566 private final String mUnknownArtist; 567 private final StringBuilder mBuffer = new StringBuilder(); 568 private final Object[] mFormatArgs = new Object[1]; 569 private final Object[] mFormatArgs3 = new Object[3]; 570 private MusicAlphabetIndexer mIndexer; 571 private ArtistAlbumBrowserActivity mActivity; 572 private AsyncQueryHandler mQueryHandler; 573 private String mConstraint = null; 574 private boolean mConstraintIsValid = false; 575 576 static class ViewHolder { 577 TextView line1; 578 TextView line2; 579 ImageView play_indicator; 580 ImageView icon; 581 } 582 583 class QueryHandler extends AsyncQueryHandler { 584 QueryHandler(ContentResolver res) { 585 super(res); 586 } 587 588 @Override 589 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 590 //Log.i("@@@", "query complete"); 591 mActivity.init(cursor); 592 } 593 } 594 595 ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity, 596 Cursor cursor, int glayout, String[] gfrom, int[] gto, 597 int clayout, String[] cfrom, int[] cto) { 598 super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto); 599 mActivity = currentactivity; 600 mQueryHandler = new QueryHandler(context.getContentResolver()); 601 602 Resources r = context.getResources(); 603 mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list); 604 mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list); 605 // no filter or dither, it's a lot faster and we can't tell the difference 606 mDefaultAlbumIcon.setFilterBitmap(false); 607 mDefaultAlbumIcon.setDither(false); 608 609 mContext = context; 610 getColumnIndices(cursor); 611 mResources = context.getResources(); 612 mAlbumSongSeparator = context.getString(R.string.albumsongseparator); 613 mUnknownAlbum = context.getString(R.string.unknown_album_name); 614 mUnknownArtist = context.getString(R.string.unknown_artist_name); 615 } 616 617 private void getColumnIndices(Cursor cursor) { 618 if (cursor != null) { 619 mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID); 620 mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST); 621 mGroupAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS); 622 mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS); 623 if (mIndexer != null) { 624 mIndexer.setCursor(cursor); 625 } else { 626 mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx, 627 mResources.getString(R.string.fast_scroll_alphabet)); 628 } 629 } 630 } 631 632 public void setActivity(ArtistAlbumBrowserActivity newactivity) { 633 mActivity = newactivity; 634 } 635 636 public AsyncQueryHandler getQueryHandler() { 637 return mQueryHandler; 638 } 639 640 @Override 641 public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { 642 View v = super.newGroupView(context, cursor, isExpanded, parent); 643 ImageView iv = (ImageView) v.findViewById(R.id.icon); 644 ViewGroup.LayoutParams p = iv.getLayoutParams(); 645 p.width = ViewGroup.LayoutParams.WRAP_CONTENT; 646 p.height = ViewGroup.LayoutParams.WRAP_CONTENT; 647 ViewHolder vh = new ViewHolder(); 648 vh.line1 = (TextView) v.findViewById(R.id.line1); 649 vh.line2 = (TextView) v.findViewById(R.id.line2); 650 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 651 vh.icon = (ImageView) v.findViewById(R.id.icon); 652 vh.icon.setPadding(0, 0, 1, 0); 653 v.setTag(vh); 654 return v; 655 } 656 657 @Override 658 public View newChildView(Context context, Cursor cursor, boolean isLastChild, 659 ViewGroup parent) { 660 View v = super.newChildView(context, cursor, isLastChild, parent); 661 ViewHolder vh = new ViewHolder(); 662 vh.line1 = (TextView) v.findViewById(R.id.line1); 663 vh.line2 = (TextView) v.findViewById(R.id.line2); 664 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 665 vh.icon = (ImageView) v.findViewById(R.id.icon); 666 vh.icon.setBackgroundDrawable(mDefaultAlbumIcon); 667 vh.icon.setPadding(0, 0, 1, 0); 668 v.setTag(vh); 669 return v; 670 } 671 672 @Override 673 public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) { 674 675 ViewHolder vh = (ViewHolder) view.getTag(); 676 677 String artist = cursor.getString(mGroupArtistIdx); 678 String displayartist = artist; 679 boolean unknown = artist == null || artist.equals(MediaStore.UNKNOWN_STRING); 680 if (unknown) { 681 displayartist = mUnknownArtist; 682 } 683 vh.line1.setText(displayartist); 684 685 int numalbums = cursor.getInt(mGroupAlbumIdx); 686 int numsongs = cursor.getInt(mGroupSongIdx); 687 688 String songs_albums = MusicUtils.makeAlbumsLabel(context, 689 numalbums, numsongs, unknown); 690 691 vh.line2.setText(songs_albums); 692 693 long currentartistid = MusicUtils.getCurrentArtistId(); 694 long artistid = cursor.getLong(mGroupArtistIdIdx); 695 if (currentartistid == artistid && !isexpanded) { 696 vh.play_indicator.setImageDrawable(mNowPlayingOverlay); 697 } else { 698 vh.play_indicator.setImageDrawable(null); 699 } 700 } 701 702 @Override 703 public void bindChildView(View view, Context context, Cursor cursor, boolean islast) { 704 705 ViewHolder vh = (ViewHolder) view.getTag(); 706 707 String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 708 String displayname = name; 709 boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING); 710 if (unknown) { 711 displayname = mUnknownAlbum; 712 } 713 vh.line1.setText(displayname); 714 715 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS)); 716 int numartistsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST)); 717 718 final StringBuilder builder = mBuffer; 719 builder.delete(0, builder.length()); 720 if (unknown) { 721 numsongs = numartistsongs; 722 } 723 724 if (numsongs == 1) { 725 builder.append(context.getString(R.string.onesong)); 726 } else { 727 if (numsongs == numartistsongs) { 728 final Object[] args = mFormatArgs; 729 args[0] = numsongs; 730 builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args)); 731 } else { 732 final Object[] args = mFormatArgs3; 733 args[0] = numsongs; 734 args[1] = numartistsongs; 735 args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 736 builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args)); 737 } 738 } 739 vh.line2.setText(builder.toString()); 740 741 ImageView iv = vh.icon; 742 // We don't actually need the path to the thumbnail file, 743 // we just use it to see if there is album art or not 744 String art = cursor.getString(cursor.getColumnIndexOrThrow( 745 MediaStore.Audio.Albums.ALBUM_ART)); 746 if (unknown || art == null || art.length() == 0) { 747 iv.setBackgroundDrawable(mDefaultAlbumIcon); 748 iv.setImageDrawable(null); 749 } else { 750 long artIndex = cursor.getLong(0); 751 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon); 752 iv.setImageDrawable(d); 753 } 754 755 long currentalbumid = MusicUtils.getCurrentAlbumId(); 756 long aid = cursor.getLong(0); 757 iv = vh.play_indicator; 758 if (currentalbumid == aid) { 759 iv.setImageDrawable(mNowPlayingOverlay); 760 } else { 761 iv.setImageDrawable(null); 762 } 763 } 764 765 766 @Override 767 protected Cursor getChildrenCursor(Cursor groupCursor) { 768 769 long id = groupCursor.getLong(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)); 770 771 String[] cols = new String[] { 772 MediaStore.Audio.Albums._ID, 773 MediaStore.Audio.Albums.ALBUM, 774 MediaStore.Audio.Albums.NUMBER_OF_SONGS, 775 MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST, 776 MediaStore.Audio.Albums.ALBUM_ART 777 }; 778 Cursor c = MusicUtils.query(mActivity, 779 MediaStore.Audio.Artists.Albums.getContentUri("external", id), 780 cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 781 782 class MyCursorWrapper extends CursorWrapper { 783 String mArtistName; 784 int mMagicColumnIdx; 785 MyCursorWrapper(Cursor c, String artist) { 786 super(c); 787 mArtistName = artist; 788 if (mArtistName == null || mArtistName.equals(MediaStore.UNKNOWN_STRING)) { 789 mArtistName = mUnknownArtist; 790 } 791 mMagicColumnIdx = c.getColumnCount(); 792 } 793 794 @Override 795 public String getString(int columnIndex) { 796 if (columnIndex != mMagicColumnIdx) { 797 return super.getString(columnIndex); 798 } 799 return mArtistName; 800 } 801 802 @Override 803 public int getColumnIndexOrThrow(String name) { 804 if (MediaStore.Audio.Albums.ARTIST.equals(name)) { 805 return mMagicColumnIdx; 806 } 807 return super.getColumnIndexOrThrow(name); 808 } 809 810 @Override 811 public String getColumnName(int idx) { 812 if (idx != mMagicColumnIdx) { 813 return super.getColumnName(idx); 814 } 815 return MediaStore.Audio.Albums.ARTIST; 816 } 817 818 @Override 819 public int getColumnCount() { 820 return super.getColumnCount() + 1; 821 } 822 } 823 return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx)); 824 } 825 826 @Override 827 public void changeCursor(Cursor cursor) { 828 if (mActivity.isFinishing() && cursor != null) { 829 cursor.close(); 830 cursor = null; 831 } 832 if (cursor != mActivity.mArtistCursor) { 833 mActivity.mArtistCursor = cursor; 834 getColumnIndices(cursor); 835 super.changeCursor(cursor); 836 } 837 } 838 839 @Override 840 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 841 String s = constraint.toString(); 842 if (mConstraintIsValid && ( 843 (s == null && mConstraint == null) || 844 (s != null && s.equals(mConstraint)))) { 845 return getCursor(); 846 } 847 Cursor c = mActivity.getArtistCursor(null, s); 848 mConstraint = s; 849 mConstraintIsValid = true; 850 return c; 851 } 852 853 public Object[] getSections() { 854 return mIndexer.getSections(); 855 } 856 857 public int getPositionForSection(int sectionIndex) { 858 return mIndexer.getPositionForSection(sectionIndex); 859 } 860 861 public int getSectionForPosition(int position) { 862 return 0; 863 } 864 } 865 866 private Cursor mArtistCursor; 867 868 public void onServiceConnected(ComponentName name, IBinder service) { 869 MusicUtils.updateNowPlaying(this); 870 } 871 872 public void onServiceDisconnected(ComponentName name) { 873 finish(); 874 } 875} 876 877