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