ArtistAlbumBrowserActivity.java revision d99bc1e9481c68514a4e5eea4c3f429843b701b4
111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert/* 211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * Copyright (C) 2007 The Android Open Source Project 311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * 411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * Licensed under the Apache License, Version 2.0 (the "License"); 511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * you may not use this file except in compliance with the License. 611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * You may obtain a copy of the License at 711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * 811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * http://www.apache.org/licenses/LICENSE-2.0 911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * 1011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * Unless required by applicable law or agreed to in writing, software 1111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * distributed under the License is distributed on an "AS IS" BASIS, 1211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * See the License for the specific language governing permissions and 1411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert * limitations under the License. 1511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert */ 1611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 1711cd02dfb91661c65134cac258cf5924270e9d2Dan Albertpackage com.android.music; 1811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 1911cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler; 2011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 2111cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.app.ExpandableListActivity; 2211cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.app.SearchManager; 2311cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.AsyncQueryHandler; 2411cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.BroadcastReceiver; 2511cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.ContentResolver; 2611cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.Context; 2711cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.Intent; 2811cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.IntentFilter; 2911cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.content.res.Resources; 3011cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.database.Cursor; 3111cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.database.CursorWrapper; 3211cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.graphics.drawable.BitmapDrawable; 3311cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.graphics.drawable.Drawable; 3411cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.media.AudioManager; 3511cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.media.MediaFile; 3611cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.net.Uri; 3711cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.os.Bundle; 3811cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.os.Handler; 3911cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.os.Message; 4011cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.provider.MediaStore; 4111cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.util.Log; 4211cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.ContextMenu; 4311cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.Menu; 4411cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.MenuItem; 4511cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.SubMenu; 4611cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.View; 4711cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.ViewGroup; 4811cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.Window; 4911cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.view.ContextMenu.ContextMenuInfo; 5011cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.CursorAdapter; 5111cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.CursorTreeAdapter; 5211cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.ExpandableListAdapter; 5311cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.ExpandableListView; 5411cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.ImageView; 5511cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.SectionIndexer; 5611cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.SimpleCursorTreeAdapter; 5711cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.TextView; 5811cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport android.widget.ExpandableListView.ExpandableListContextMenuInfo; 5911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 6011cd02dfb91661c65134cac258cf5924270e9d2Dan Albertimport java.text.Collator; 6111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 6211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 6311cd02dfb91661c65134cac258cf5924270e9d2Dan Albertpublic class ArtistAlbumBrowserActivity extends ExpandableListActivity 6411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert implements View.OnCreateContextMenuListener, MusicUtils.Defs 6511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert{ 6611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private String mCurrentArtistId; 6711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private String mCurrentArtistName; 6811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private String mCurrentAlbumId; 6911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private String mCurrentAlbumName; 7011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private String mCurrentArtistNameForAlbum; 7111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert boolean mIsUnknownArtist; 7211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert boolean mIsUnknownAlbum; 7311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private ArtistAlbumListAdapter mAdapter; 7411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private boolean mAdapterSent; 7511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert private final static int SEARCH = CHILD_MENU_BASE; 7611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 7711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert public ArtistAlbumBrowserActivity() 7811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert { 7911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 8011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 8111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert /** Called when the activity is first created. */ 8211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert @Override 8311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert public void onCreate(Bundle icicle) { 8411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert super.onCreate(icicle); 8511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 8611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert setVolumeControlStream(AudioManager.STREAM_MUSIC); 8711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert if (icicle != null) { 8811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mCurrentAlbumId = icicle.getString("selectedalbum"); 8911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mCurrentAlbumName = icicle.getString("selectedalbumname"); 9011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mCurrentArtistId = icicle.getString("selectedartist"); 9111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mCurrentArtistName = icicle.getString("selectedartistname"); 9211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 9311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert MusicUtils.bindToService(this); 9411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 9511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert IntentFilter f = new IntentFilter(); 9611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 9711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 9811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 9911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert f.addDataScheme("file"); 10011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert registerReceiver(mScanListener, f); 10111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 10211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert setContentView(R.layout.media_picker_activity_expanding); 10311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert ExpandableListView lv = getExpandableListView(); 10411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert lv.setFastScrollEnabled(true); 10511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert lv.setOnCreateContextMenuListener(this); 10611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert lv.setTextFilterEnabled(true); 10711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 10811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance(); 10911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert if (mAdapter == null) { 11011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert //Log.i("@@@", "starting query"); 11111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mAdapter = new ArtistAlbumListAdapter( 11211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert getApplication(), 11311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert this, 11411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert null, // cursor 11511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert R.layout.track_list_item_group, 11611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert new String[] {}, 11711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert new int[] {}, 11811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert R.layout.track_list_item_child, 11911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert new String[] {}, 12011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert new int[] {}); 12111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert setListAdapter(mAdapter); 12211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert setTitle(R.string.working_artists); 12311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert getArtistCursor(mAdapter.getQueryHandler(), null); 12411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } else { 12511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mAdapter.setActivity(this); 12611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert setListAdapter(mAdapter); 12711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mArtistCursor = mAdapter.getCursor(); 12811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert if (mArtistCursor != null) { 12911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert init(mArtistCursor); 13011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } else { 13111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert getArtistCursor(mAdapter.getQueryHandler(), null); 13211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 13311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 13411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 13511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 13611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert @Override 13711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert public Object onRetainNonConfigurationInstance() { 13811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert mAdapterSent = true; 13911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert return mAdapter; 14011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 14111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 14211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert @Override 14311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert public void onSaveInstanceState(Bundle outcicle) { 14411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert // need to store the selected item so we don't lose it in case 14511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert // of an orientation switch. Otherwise we could lose it while 14611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert // in the middle of specifying a playlist to add the item to. 14711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert outcicle.putString("selectedalbum", mCurrentAlbumId); 14811cd02dfb91661c65134cac258cf5924270e9d2Dan Albert outcicle.putString("selectedalbumname", mCurrentAlbumName); 14911cd02dfb91661c65134cac258cf5924270e9d2Dan Albert outcicle.putString("selectedartist", mCurrentArtistId); 15011cd02dfb91661c65134cac258cf5924270e9d2Dan Albert outcicle.putString("selectedartistname", mCurrentArtistName); 15111cd02dfb91661c65134cac258cf5924270e9d2Dan Albert super.onSaveInstanceState(outcicle); 15211cd02dfb91661c65134cac258cf5924270e9d2Dan Albert } 15311cd02dfb91661c65134cac258cf5924270e9d2Dan Albert 15411cd02dfb91661c65134cac258cf5924270e9d2Dan Albert @Override 15511cd02dfb91661c65134cac258cf5924270e9d2Dan Albert public void onDestroy() { 15611cd02dfb91661c65134cac258cf5924270e9d2Dan Albert MusicUtils.unbindFromService(this); 15711cd02dfb91661c65134cac258cf5924270e9d2Dan Albert // If we have an adapter and didn't send it off to another activity yet, we should 158 // close its cursor, which we do by assigning a null cursor to it. Doing this 159 // instead of closing the cursor directly keeps the framework from accessing 160 // the closed cursor later. 161 if (!mAdapterSent && mAdapter != null) { 162 mAdapter.changeCursor(null); 163 } 164 // Because we pass the adapter to the next activity, we need to make 165 // sure it doesn't keep a reference to this activity. We can do this 166 // by clearing its DatasetObservers, which setListAdapter(null) does. 167 setListAdapter(null); 168 mAdapter = null; 169 unregisterReceiver(mScanListener); 170 setListAdapter(null); 171 super.onDestroy(); 172 } 173 174 @Override 175 public void onResume() { 176 super.onResume(); 177 IntentFilter f = new IntentFilter(); 178 f.addAction(MediaPlaybackService.META_CHANGED); 179 f.addAction(MediaPlaybackService.QUEUE_CHANGED); 180 registerReceiver(mTrackListListener, f); 181 mTrackListListener.onReceive(null, null); 182 183 MusicUtils.setSpinnerState(this); 184 } 185 186 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { 187 @Override 188 public void onReceive(Context context, Intent intent) { 189 getExpandableListView().invalidateViews(); 190 } 191 }; 192 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 193 @Override 194 public void onReceive(Context context, Intent intent) { 195 MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this); 196 mReScanHandler.sendEmptyMessage(0); 197 if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 198 MusicUtils.clearAlbumArtCache(); 199 } 200 } 201 }; 202 203 private Handler mReScanHandler = new Handler() { 204 @Override 205 public void handleMessage(Message msg) { 206 if (mAdapter != null) { 207 getArtistCursor(mAdapter.getQueryHandler(), null); 208 } 209 } 210 }; 211 212 @Override 213 public void onPause() { 214 unregisterReceiver(mTrackListListener); 215 mReScanHandler.removeCallbacksAndMessages(null); 216 super.onPause(); 217 } 218 219 public void init(Cursor c) { 220 221 if (mAdapter == null) { 222 return; 223 } 224 mAdapter.changeCursor(c); // also sets mArtistCursor 225 226 if (mArtistCursor == null) { 227 MusicUtils.displayDatabaseError(this); 228 closeContextMenu(); 229 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 230 return; 231 } 232 233 MusicUtils.hideDatabaseError(this); 234 setTitle(); 235 } 236 237 private void setTitle() { 238 setTitle(R.string.artists_title); 239 } 240 241 @Override 242 public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { 243 244 mCurrentAlbumId = Long.valueOf(id).toString(); 245 246 Intent intent = new Intent(Intent.ACTION_PICK); 247 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 248 intent.putExtra("album", mCurrentAlbumId); 249 Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition); 250 String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM)); 251 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) { 252 // unknown album, so we should include the artist ID to limit the songs to songs only by that artist 253 mArtistCursor.moveToPosition(groupPosition); 254 mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID)); 255 intent.putExtra("artist", mCurrentArtistId); 256 } 257 startActivity(intent); 258 return true; 259 } 260 261 @Override 262 public boolean onCreateOptionsMenu(Menu menu) { 263 super.onCreateOptionsMenu(menu); 264 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library); 265 menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback); 266 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle); 267 return true; 268 } 269 270 @Override 271 public boolean onPrepareOptionsMenu(Menu menu) { 272 menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded()); 273 return super.onPrepareOptionsMenu(menu); 274 } 275 276 @Override 277 public boolean onOptionsItemSelected(MenuItem item) { 278 Intent intent; 279 Cursor cursor; 280 switch (item.getItemId()) { 281 case GOTO_START: 282 intent = new Intent(); 283 intent.setClass(this, MusicBrowserActivity.class); 284 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 285 startActivity(intent); 286 return true; 287 288 case GOTO_PLAYBACK: 289 intent = new Intent("com.android.music.PLAYBACK_VIEWER"); 290 startActivity(intent); 291 return true; 292 293 case SHUFFLE_ALL: 294 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 295 new String [] { MediaStore.Audio.Media._ID}, 296 MediaStore.Audio.Media.IS_MUSIC + "=1", null, 297 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 298 if (cursor != null) { 299 MusicUtils.shuffleAll(this, cursor); 300 cursor.close(); 301 } 302 return true; 303 } 304 return super.onOptionsItemSelected(item); 305 } 306 307 @Override 308 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { 309 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection); 310 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist); 311 MusicUtils.makePlaylistMenu(this, sub); 312 menu.add(0, DELETE_ITEM, 0, R.string.delete_item); 313 314 ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn; 315 316 int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition); 317 int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition); 318 int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition); 319 if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { 320 if (gpos == -1) { 321 // this shouldn't happen 322 Log.d("Artist/Album", "no group"); 323 return; 324 } 325 gpos = gpos - getExpandableListView().getHeaderViewsCount(); 326 mArtistCursor.moveToPosition(gpos); 327 mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)); 328 mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 329 mCurrentAlbumId = null; 330 mIsUnknownArtist = mCurrentArtistName == null || 331 mCurrentArtistName.equals(MediaFile.UNKNOWN_STRING); 332 mIsUnknownAlbum = true; 333 if (mIsUnknownArtist) { 334 menu.setHeaderTitle(getString(R.string.unknown_artist_name)); 335 } else { 336 menu.setHeaderTitle(mCurrentArtistName); 337 menu.add(0, SEARCH, 0, R.string.search_title); 338 } 339 return; 340 } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { 341 if (cpos == -1) { 342 // this shouldn't happen 343 Log.d("Artist/Album", "no child"); 344 return; 345 } 346 Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos); 347 c.moveToPosition(cpos); 348 mCurrentArtistId = null; 349 mCurrentAlbumId = Long.valueOf(mi.id).toString(); 350 mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 351 gpos = gpos - getExpandableListView().getHeaderViewsCount(); 352 mArtistCursor.moveToPosition(gpos); 353 mCurrentArtistNameForAlbum = mArtistCursor.getString( 354 mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 355 mIsUnknownArtist = mCurrentArtistNameForAlbum == null || 356 mCurrentArtistNameForAlbum.equals(MediaFile.UNKNOWN_STRING); 357 mIsUnknownAlbum = mCurrentAlbumName == null || 358 mCurrentAlbumName.equals(MediaFile.UNKNOWN_STRING); 359 if (mIsUnknownAlbum) { 360 menu.setHeaderTitle(getString(R.string.unknown_album_name)); 361 } else { 362 menu.setHeaderTitle(mCurrentAlbumName); 363 } 364 if (!mIsUnknownAlbum || !mIsUnknownArtist) { 365 menu.add(0, SEARCH, 0, R.string.search_title); 366 } 367 } 368 } 369 370 @Override 371 public boolean onContextItemSelected(MenuItem item) { 372 switch (item.getItemId()) { 373 case PLAY_SELECTION: { 374 // play everything by the selected artist 375 long [] list = 376 mCurrentArtistId != null ? 377 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 378 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 379 380 MusicUtils.playAll(this, list, 0); 381 return true; 382 } 383 384 case QUEUE: { 385 long [] list = 386 mCurrentArtistId != null ? 387 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 388 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 389 MusicUtils.addToCurrentPlaylist(this, list); 390 return true; 391 } 392 393 case NEW_PLAYLIST: { 394 Intent intent = new Intent(); 395 intent.setClass(this, CreatePlaylist.class); 396 startActivityForResult(intent, NEW_PLAYLIST); 397 return true; 398 } 399 400 case PLAYLIST_SELECTED: { 401 long [] list = 402 mCurrentArtistId != null ? 403 MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)) 404 : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 405 long playlist = item.getIntent().getLongExtra("playlist", 0); 406 MusicUtils.addToPlaylist(this, list, playlist); 407 return true; 408 } 409 410 case DELETE_ITEM: { 411 long [] list; 412 String desc; 413 if (mCurrentArtistId != null) { 414 list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)); 415 String f = getString(R.string.delete_artist_desc); 416 desc = String.format(f, mCurrentArtistName); 417 } else { 418 list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 419 String f = getString(R.string.delete_album_desc); 420 desc = String.format(f, mCurrentAlbumName); 421 } 422 Bundle b = new Bundle(); 423 b.putString("description", desc); 424 b.putLongArray("items", list); 425 Intent intent = new Intent(); 426 intent.setClass(this, DeleteItems.class); 427 intent.putExtras(b); 428 startActivityForResult(intent, -1); 429 return true; 430 } 431 432 case SEARCH: 433 doSearch(); 434 return true; 435 } 436 return super.onContextItemSelected(item); 437 } 438 439 void doSearch() { 440 CharSequence title = null; 441 String query = null; 442 443 Intent i = new Intent(); 444 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); 445 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 446 447 if (mCurrentArtistId != null) { 448 title = mCurrentArtistName; 449 query = mCurrentArtistName; 450 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName); 451 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE); 452 } else { 453 if (mIsUnknownAlbum) { 454 title = query = mCurrentArtistNameForAlbum; 455 } else { 456 title = query = mCurrentAlbumName; 457 if (!mIsUnknownArtist) { 458 query = query + " " + mCurrentArtistNameForAlbum; 459 } 460 } 461 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); 462 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); 463 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE); 464 } 465 title = getString(R.string.mediasearch, title); 466 i.putExtra(SearchManager.QUERY, query); 467 468 startActivity(Intent.createChooser(i, title)); 469 } 470 471 @Override 472 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 473 switch (requestCode) { 474 case SCAN_DONE: 475 if (resultCode == RESULT_CANCELED) { 476 finish(); 477 } else { 478 getArtistCursor(mAdapter.getQueryHandler(), null); 479 } 480 break; 481 482 case NEW_PLAYLIST: 483 if (resultCode == RESULT_OK) { 484 Uri uri = intent.getData(); 485 if (uri != null) { 486 long [] list = null; 487 if (mCurrentArtistId != null) { 488 list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId)); 489 } else if (mCurrentAlbumId != null) { 490 list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId)); 491 } 492 MusicUtils.addToPlaylist(this, list, Long.parseLong(uri.getLastPathSegment())); 493 } 494 } 495 break; 496 } 497 } 498 499 private Cursor getArtistCursor(AsyncQueryHandler async, String filter) { 500 501 StringBuilder where = new StringBuilder(); 502 where.append(MediaStore.Audio.Artists.ARTIST + " != ''"); 503 504 // Add in the filtering constraints 505 String [] keywords = null; 506 if (filter != null) { 507 String [] searchWords = filter.split(" "); 508 keywords = new String[searchWords.length]; 509 Collator col = Collator.getInstance(); 510 col.setStrength(Collator.PRIMARY); 511 for (int i = 0; i < searchWords.length; i++) { 512 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%'; 513 } 514 for (int i = 0; i < searchWords.length; i++) { 515 where.append(" AND "); 516 where.append(MediaStore.Audio.Media.ARTIST_KEY + " LIKE ?"); 517 } 518 } 519 520 String whereclause = where.toString(); 521 String[] cols = new String[] { 522 MediaStore.Audio.Artists._ID, 523 MediaStore.Audio.Artists.ARTIST, 524 MediaStore.Audio.Artists.NUMBER_OF_ALBUMS, 525 MediaStore.Audio.Artists.NUMBER_OF_TRACKS 526 }; 527 Cursor ret = null; 528 if (async != null) { 529 async.startQuery(0, null, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 530 cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY); 531 } else { 532 ret = MusicUtils.query(this, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 533 cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY); 534 } 535 return ret; 536 } 537 538 static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer { 539 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, 581 int clayout, String[] cfrom, 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 = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS); 606 mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS); 607 if (mIndexer != null) { 608 mIndexer.setCursor(cursor); 609 } else { 610 mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx, 611 mResources.getString(com.android.internal.R.string.fast_scroll_alphabet)); 612 } 613 } 614 } 615 616 public void setActivity(ArtistAlbumBrowserActivity newactivity) { 617 mActivity = newactivity; 618 } 619 620 public AsyncQueryHandler getQueryHandler() { 621 return mQueryHandler; 622 } 623 624 @Override 625 public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { 626 View v = super.newGroupView(context, cursor, isExpanded, parent); 627 ImageView iv = (ImageView) v.findViewById(R.id.icon); 628 ViewGroup.LayoutParams p = iv.getLayoutParams(); 629 p.width = ViewGroup.LayoutParams.WRAP_CONTENT; 630 p.height = ViewGroup.LayoutParams.WRAP_CONTENT; 631 ViewHolder vh = new ViewHolder(); 632 vh.line1 = (TextView) v.findViewById(R.id.line1); 633 vh.line2 = (TextView) v.findViewById(R.id.line2); 634 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 635 vh.icon = (ImageView) v.findViewById(R.id.icon); 636 vh.icon.setPadding(0, 0, 1, 0); 637 v.setTag(vh); 638 return v; 639 } 640 641 @Override 642 public View newChildView(Context context, Cursor cursor, boolean isLastChild, 643 ViewGroup parent) { 644 View v = super.newChildView(context, cursor, isLastChild, parent); 645 ViewHolder vh = new ViewHolder(); 646 vh.line1 = (TextView) v.findViewById(R.id.line1); 647 vh.line2 = (TextView) v.findViewById(R.id.line2); 648 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); 649 vh.icon = (ImageView) v.findViewById(R.id.icon); 650 vh.icon.setBackgroundDrawable(mDefaultAlbumIcon); 651 vh.icon.setPadding(0, 0, 1, 0); 652 v.setTag(vh); 653 return v; 654 } 655 656 @Override 657 public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) { 658 659 ViewHolder vh = (ViewHolder) view.getTag(); 660 661 String artist = cursor.getString(mGroupArtistIdx); 662 String displayartist = artist; 663 boolean unknown = artist == null || artist.equals(MediaFile.UNKNOWN_STRING); 664 if (unknown) { 665 displayartist = mUnknownArtist; 666 } 667 vh.line1.setText(displayartist); 668 669 int numalbums = cursor.getInt(mGroupAlbumIdx); 670 int numsongs = cursor.getInt(mGroupSongIdx); 671 672 String songs_albums = MusicUtils.makeAlbumsLabel(context, 673 numalbums, numsongs, unknown); 674 675 vh.line2.setText(songs_albums); 676 677 long currentartistid = MusicUtils.getCurrentArtistId(); 678 long artistid = cursor.getLong(mGroupArtistIdIdx); 679 if (currentartistid == artistid && !isexpanded) { 680 vh.play_indicator.setImageDrawable(mNowPlayingOverlay); 681 } else { 682 vh.play_indicator.setImageDrawable(null); 683 } 684 } 685 686 @Override 687 public void bindChildView(View view, Context context, Cursor cursor, boolean islast) { 688 689 ViewHolder vh = (ViewHolder) view.getTag(); 690 691 String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); 692 String displayname = name; 693 boolean unknown = name == null || name.equals(MediaFile.UNKNOWN_STRING); 694 if (unknown) { 695 displayname = mUnknownAlbum; 696 } 697 vh.line1.setText(displayname); 698 699 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS)); 700 int numartistsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST)); 701 702 final StringBuilder builder = mBuffer; 703 builder.delete(0, builder.length()); 704 if (unknown) { 705 numsongs = numartistsongs; 706 } 707 708 if (numsongs == 1) { 709 builder.append(context.getString(R.string.onesong)); 710 } else { 711 if (numsongs == numartistsongs) { 712 final Object[] args = mFormatArgs; 713 args[0] = numsongs; 714 builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args)); 715 } else { 716 final Object[] args = mFormatArgs3; 717 args[0] = numsongs; 718 args[1] = numartistsongs; 719 args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); 720 builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args)); 721 } 722 } 723 vh.line2.setText(builder.toString()); 724 725 ImageView iv = vh.icon; 726 // We don't actually need the path to the thumbnail file, 727 // we just use it to see if there is album art or not 728 String art = cursor.getString(cursor.getColumnIndexOrThrow( 729 MediaStore.Audio.Albums.ALBUM_ART)); 730 if (unknown || art == null || art.length() == 0) { 731 iv.setBackgroundDrawable(mDefaultAlbumIcon); 732 iv.setImageDrawable(null); 733 } else { 734 long artIndex = cursor.getLong(0); 735 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon); 736 iv.setImageDrawable(d); 737 } 738 739 long currentalbumid = MusicUtils.getCurrentAlbumId(); 740 long aid = cursor.getLong(0); 741 iv = vh.play_indicator; 742 if (currentalbumid == aid) { 743 iv.setImageDrawable(mNowPlayingOverlay); 744 } else { 745 iv.setImageDrawable(null); 746 } 747 } 748 749 750 @Override 751 protected Cursor getChildrenCursor(Cursor groupCursor) { 752 753 long id = groupCursor.getLong(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)); 754 755 String[] cols = new String[] { 756 MediaStore.Audio.Albums._ID, 757 MediaStore.Audio.Albums.ALBUM, 758 MediaStore.Audio.Albums.NUMBER_OF_SONGS, 759 MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST, 760 MediaStore.Audio.Albums.ALBUM_ART 761 }; 762 Cursor c = MusicUtils.query(mActivity, 763 MediaStore.Audio.Artists.Albums.getContentUri("external", id), 764 cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); 765 766 class MyCursorWrapper extends CursorWrapper { 767 String mArtistName; 768 int mMagicColumnIdx; 769 MyCursorWrapper(Cursor c, String artist) { 770 super(c); 771 mArtistName = artist; 772 if (mArtistName == null || mArtistName.equals(MediaFile.UNKNOWN_STRING)) { 773 mArtistName = mUnknownArtist; 774 } 775 mMagicColumnIdx = c.getColumnCount(); 776 } 777 778 @Override 779 public String getString(int columnIndex) { 780 if (columnIndex != mMagicColumnIdx) { 781 return super.getString(columnIndex); 782 } 783 return mArtistName; 784 } 785 786 @Override 787 public int getColumnIndexOrThrow(String name) { 788 if (MediaStore.Audio.Albums.ARTIST.equals(name)) { 789 return mMagicColumnIdx; 790 } 791 return super.getColumnIndexOrThrow(name); 792 } 793 794 @Override 795 public String getColumnName(int idx) { 796 if (idx != mMagicColumnIdx) { 797 return super.getColumnName(idx); 798 } 799 return MediaStore.Audio.Albums.ARTIST; 800 } 801 802 @Override 803 public int getColumnCount() { 804 return super.getColumnCount() + 1; 805 } 806 } 807 return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx)); 808 } 809 810 @Override 811 public void changeCursor(Cursor cursor) { 812 if (mActivity.isFinishing() && cursor != null) { 813 cursor.close(); 814 cursor = null; 815 } 816 if (cursor != mActivity.mArtistCursor) { 817 mActivity.mArtistCursor = cursor; 818 getColumnIndices(cursor); 819 super.changeCursor(cursor); 820 } 821 } 822 823 @Override 824 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 825 String s = constraint.toString(); 826 if (mConstraintIsValid && ( 827 (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 853