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