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 
17 package com.android.music;
18 
19 import com.android.music.MusicUtils.ServiceToken;
20 import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler;
21 
22 import android.app.ExpandableListActivity;
23 import android.app.SearchManager;
24 import android.content.AsyncQueryHandler;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.ServiceConnection;
32 import android.content.res.Resources;
33 import android.database.Cursor;
34 import android.database.CursorWrapper;
35 import android.graphics.drawable.BitmapDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.media.AudioManager;
38 import android.net.Uri;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Message;
43 import android.os.Parcel;
44 import android.os.Parcelable;
45 import android.provider.MediaStore;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.SparseArray;
49 import android.view.ContextMenu;
50 import android.view.Menu;
51 import android.view.MenuItem;
52 import android.view.SubMenu;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.view.Window;
56 import android.view.ContextMenu.ContextMenuInfo;
57 import android.widget.ExpandableListView;
58 import android.widget.ImageView;
59 import android.widget.SectionIndexer;
60 import android.widget.SimpleCursorTreeAdapter;
61 import android.widget.TextView;
62 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
63 
64 import java.text.Collator;
65 
66 
67 public 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
onCreate(Bundle icicle)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
onRetainNonConfigurationInstance()141     public Object onRetainNonConfigurationInstance() {
142         mAdapterSent = true;
143         return mAdapter;
144     }
145 
146     @Override
onSaveInstanceState(Bundle outcicle)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
onDestroy()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
onResume()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
onPause()227     public void onPause() {
228         unregisterReceiver(mTrackListListener);
229         mReScanHandler.removeCallbacksAndMessages(null);
230         super.onPause();
231     }
232 
init(Cursor c)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 
setTitle()259     private void setTitle() {
260         setTitle(R.string.artists_title);
261     }
262 
263     @Override
onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)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
onCreateOptionsMenu(Menu menu)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
onPrepareOptionsMenu(Menu menu)292     public boolean onPrepareOptionsMenu(Menu menu) {
293         MusicUtils.setPartyShuffleMenuIcon(menu);
294         return super.onPrepareOptionsMenu(menu);
295     }
296 
297     @Override
onOptionsItemSelected(MenuItem item)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
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn)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
onContextItemSelected(MenuItem item)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 
doSearch()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
onActivityResult(int requestCode, int resultCode, Intent intent)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 
getArtistCursor(AsyncQueryHandler async, String filter)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 {
QueryHandler(ContentResolver res)578             QueryHandler(ContentResolver res) {
579                 super(res);
580             }
581 
582             @Override
onQueryComplete(int token, Object cookie, Cursor cursor)583             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
584                 //Log.i("@@@", "query complete");
585                 mActivity.init(cursor);
586             }
587         }
588 
ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity, Cursor cursor, int glayout, String[] gfrom, int[] gto, int clayout, String[] cfrom, int[] cto)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 
getColumnIndices(Cursor cursor)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 
setActivity(ArtistAlbumBrowserActivity newactivity)626         public void setActivity(ArtistAlbumBrowserActivity newactivity) {
627             mActivity = newactivity;
628         }
629 
getQueryHandler()630         public AsyncQueryHandler getQueryHandler() {
631             return mQueryHandler;
632         }
633 
634         @Override
newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent)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
newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent)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
bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded)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
bindChildView(View view, Context context, Cursor cursor, boolean islast)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
getChildrenCursor(Cursor groupCursor)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
changeCursor(Cursor cursor)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
runQueryOnBackgroundThread(CharSequence constraint)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 
getSections()847         public Object[] getSections() {
848             return mIndexer.getSections();
849         }
850 
getPositionForSection(int sectionIndex)851         public int getPositionForSection(int sectionIndex) {
852             return mIndexer.getPositionForSection(sectionIndex);
853         }
854 
getSectionForPosition(int position)855         public int getSectionForPosition(int position) {
856             return 0;
857         }
858     }
859 
860     private Cursor mArtistCursor;
861 
onServiceConnected(ComponentName name, IBinder service)862     public void onServiceConnected(ComponentName name, IBinder service) {
863         MusicUtils.updateNowPlaying(this);
864     }
865 
onServiceDisconnected(ComponentName name)866     public void onServiceDisconnected(ComponentName name) {
867         finish();
868     }
869 }
870 
871