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