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