OSDN Git Service

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