AlbumBrowserActivity.java revision 6cb8bc92e0ca524a76a6fa3f6814b43ea9a3b30d
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 java.text.Collator;
20
21import android.app.ListActivity;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.BroadcastReceiver;
26import android.content.res.Resources;
27import android.database.Cursor;
28import android.media.AudioManager;
29import android.media.MediaFile;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.provider.MediaStore;
35import android.view.ContextMenu;
36import android.view.Menu;
37import android.view.MenuItem;
38import android.view.SubMenu;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42import android.view.ContextMenu.ContextMenuInfo;
43import android.widget.AdapterView.AdapterContextMenuInfo;
44import android.widget.ImageView;
45import android.widget.ListView;
46import android.widget.SimpleCursorAdapter;
47import android.widget.TextView;
48import android.graphics.Bitmap;
49import android.graphics.BitmapFactory;
50import android.graphics.drawable.BitmapDrawable;
51import android.graphics.drawable.Drawable;
52
53public class AlbumBrowserActivity extends ListActivity
54    implements View.OnCreateContextMenuListener, MusicUtils.Defs
55{
56    private String mCurrentAlbumId;
57    private String mCurrentAlbumName;
58
59    public AlbumBrowserActivity()
60    {
61    }
62
63    /** Called when the activity is first created. */
64    @Override
65    public void onCreate(Bundle icicle)
66    {
67        if (icicle != null) {
68            mCurrentAlbumId = icicle.getString("selectedalbum");
69            mArtistId = icicle.getString("artist");
70        } else {
71            mArtistId = getIntent().getStringExtra("artist");
72        }
73        super.onCreate(icicle);
74        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
75        setVolumeControlStream(AudioManager.STREAM_MUSIC);
76        MusicUtils.bindToService(this);
77
78        IntentFilter f = new IntentFilter();
79        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
80        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
81        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
82        f.addDataScheme("file");
83        registerReceiver(mScanListener, f);
84
85        init();
86    }
87
88    @Override
89    public void onSaveInstanceState(Bundle outcicle) {
90        // need to store the selected item so we don't lose it in case
91        // of an orientation switch. Otherwise we could lose it while
92        // in the middle of specifying a playlist to add the item to.
93        outcicle.putString("selectedalbum", mCurrentAlbumId);
94        outcicle.putString("artist", mArtistId);
95        super.onSaveInstanceState(outcicle);
96    }
97
98    @Override
99    public void onDestroy() {
100        MusicUtils.unbindFromService(this);
101        if (mAlbumCursor != null) {
102            mAlbumCursor.close();
103        }
104        unregisterReceiver(mScanListener);
105        super.onDestroy();
106    }
107
108    @Override
109    public void onResume() {
110        super.onResume();
111        IntentFilter f = new IntentFilter();
112        f.addAction(MediaPlaybackService.META_CHANGED);
113        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
114        registerReceiver(mTrackListListener, f);
115        mTrackListListener.onReceive(null, null);
116
117        MusicUtils.setSpinnerState(this);
118    }
119
120    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
121        @Override
122        public void onReceive(Context context, Intent intent) {
123            getListView().invalidateViews();
124        }
125    };
126    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
127        @Override
128        public void onReceive(Context context, Intent intent) {
129            MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
130            mReScanHandler.sendEmptyMessage(0);
131            if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
132                MusicUtils.clearAlbumArtCache();
133            }
134        }
135    };
136
137    private Handler mReScanHandler = new Handler() {
138        public void handleMessage(Message msg) {
139            init();
140            if (mAlbumCursor == null) {
141                sendEmptyMessageDelayed(0, 1000);
142            }
143        }
144    };
145
146    @Override
147    public void onPause() {
148        unregisterReceiver(mTrackListListener);
149        mReScanHandler.removeCallbacksAndMessages(null);
150        super.onPause();
151    }
152
153    public void init() {
154
155        mAlbumCursor = getAlbumCursor(null);
156
157        /* The UI spec does not call for icons in the title bar
158        if (mArtistId != null) {
159            requestWindowFeature(Window.FEATURE_LEFT_ICON);
160        }
161        */
162        setContentView(R.layout.media_picker_activity);
163        /* The UI spec does not call for icons in the title bar
164        if (mArtistId != null) {
165            // this call needs to happen after setContentView
166            setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.person_title_icon);
167        }
168        */
169
170        if (mAlbumCursor == null) {
171            MusicUtils.displayDatabaseError(this);
172            return;
173        }
174
175        CharSequence fancyName = "";
176        if (mAlbumCursor.getCount() > 0) {
177            mAlbumCursor.moveToFirst();
178            fancyName = mAlbumCursor.getString(3);
179            if (MediaFile.UNKNOWN_STRING.equals(fancyName))
180                fancyName = getText(R.string.unknown_artist_name);
181
182            if (mArtistId != null && fancyName != null)
183                setTitle(fancyName);
184            else
185                setTitle(R.string.albums_title);
186        } else {
187            setTitle(R.string.no_albums_title);
188        }
189
190        // Map Cursor columns to views defined in media_list_item.xml
191        AlbumListAdapter adapter = new AlbumListAdapter(
192                this,
193                R.layout.track_list_item,
194                mAlbumCursor,
195                new String[] {},
196                new int[] {});
197
198        setListAdapter(adapter);
199        ListView lv = getListView();
200        lv.setOnCreateContextMenuListener(this);
201        lv.setTextFilterEnabled(true);
202    }
203
204    @Override
205    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
206        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
207        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
208        MusicUtils.makePlaylistMenu(this, sub);
209        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
210
211        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
212        mAlbumCursor.moveToPosition(mi.position);
213        mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
214        mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
215        menu.setHeaderTitle(mCurrentAlbumName);
216    }
217
218    @Override
219    public boolean onContextItemSelected(MenuItem item) {
220        switch (item.getItemId()) {
221            case PLAY_SELECTION: {
222                // play the selected album
223                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
224                MusicUtils.playAll(this, list, 0);
225                return true;
226            }
227
228            case QUEUE: {
229                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
230                MusicUtils.addToCurrentPlaylist(this, list);
231                return true;
232            }
233
234            case NEW_PLAYLIST: {
235                Intent intent = new Intent();
236                intent.setClass(this, CreatePlaylist.class);
237                startActivityForResult(intent, NEW_PLAYLIST);
238                return true;
239            }
240
241            case PLAYLIST_SELECTED: {
242                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
243                int playlist = item.getIntent().getIntExtra("playlist", 0);
244                MusicUtils.addToPlaylist(this, list, playlist);
245                return true;
246            }
247            case DELETE_ITEM: {
248                int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
249                String f = getString(R.string.delete_album_desc);
250                String desc = String.format(f, mCurrentAlbumName);
251                Bundle b = new Bundle();
252                b.putString("description", desc);
253                b.putIntArray("items", list);
254                Intent intent = new Intent();
255                intent.setClass(this, DeleteItems.class);
256                intent.putExtras(b);
257                startActivityForResult(intent, -1);
258                return true;
259            }
260
261        }
262        return super.onContextItemSelected(item);
263    }
264
265    @Override
266    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
267        switch (requestCode) {
268            case SCAN_DONE:
269                if (resultCode == RESULT_CANCELED) {
270                    finish();
271                } else {
272                    init();
273                }
274                break;
275
276            case NEW_PLAYLIST:
277                if (resultCode == RESULT_OK) {
278                    Uri uri = Uri.parse(intent.getAction());
279                    if (uri != null) {
280                        int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
281                        MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
282                    }
283                }
284                break;
285        }
286    }
287
288    @Override
289    protected void onListItemClick(ListView l, View v, int position, long id)
290    {
291        if (mHasHeader) {
292            position --;
293        }
294        Intent intent = new Intent(Intent.ACTION_PICK);
295        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
296        intent.putExtra("album", Long.valueOf(id).toString());
297        intent.putExtra("artist", mArtistId);
298        startActivity(intent);
299    }
300
301    @Override
302    public boolean onCreateOptionsMenu(Menu menu) {
303        super.onCreateOptionsMenu(menu);
304        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
305        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
306        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
307        return true;
308    }
309
310    @Override
311    public boolean onPrepareOptionsMenu(Menu menu) {
312        menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
313        return super.onPrepareOptionsMenu(menu);
314    }
315
316    @Override
317    public boolean onOptionsItemSelected(MenuItem item) {
318        Intent intent;
319        Cursor cursor;
320        switch (item.getItemId()) {
321            case GOTO_START:
322                intent = new Intent();
323                intent.setClass(this, MusicBrowserActivity.class);
324                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
325                startActivity(intent);
326                return true;
327
328            case GOTO_PLAYBACK:
329                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
330                startActivity(intent);
331                return true;
332
333            case SHUFFLE_ALL:
334                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
335                        new String [] { MediaStore.Audio.Media._ID},
336                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
337                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
338                if (cursor != null) {
339                    MusicUtils.shuffleAll(this, cursor);
340                    cursor.close();
341                }
342                return true;
343        }
344        return super.onOptionsItemSelected(item);
345    }
346
347    private Cursor getAlbumCursor(String filterstring) {
348        StringBuilder where = new StringBuilder();
349        where.append(MediaStore.Audio.Albums.ALBUM + " != ''");
350
351        // Add in the filtering constraints
352        String [] keywords = null;
353        if (filterstring != null) {
354            String [] searchWords = filterstring.split(" ");
355            keywords = new String[searchWords.length];
356            Collator col = Collator.getInstance();
357            col.setStrength(Collator.PRIMARY);
358            for (int i = 0; i < searchWords.length; i++) {
359                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
360            }
361            for (int i = 0; i < searchWords.length; i++) {
362                where.append(" AND ");
363                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
364                where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?");
365            }
366        }
367
368        String whereclause = where.toString();
369
370        String[] cols = new String[] {
371                MediaStore.Audio.Albums._ID,
372                MediaStore.Audio.Albums.ALBUM,
373                MediaStore.Audio.Albums.ALBUM_KEY,
374                MediaStore.Audio.Albums.ARTIST,
375                MediaStore.Audio.Albums.NUMBER_OF_SONGS,
376                MediaStore.Audio.Albums.ALBUM_ART
377        };
378        Cursor ret;
379        if (mArtistId != null) {
380            ret = MusicUtils.query(this,
381                    MediaStore.Audio.Artists.Albums.getContentUri("external", Long.valueOf(mArtistId)),
382                    cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
383        } else {
384            ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
385                cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
386        }
387        return ret;
388    }
389
390    class AlbumListAdapter extends SimpleCursorAdapter {
391
392        private final Drawable mNowPlayingOverlay;
393        private final BitmapDrawable mDefaultAlbumIcon;
394        private final int mAlbumIdx;
395        private final int mArtistIdx;
396        private final int mNumSongsIdx;
397        private final int mAlbumArtIndex;
398        private final Resources mResources;
399        private final StringBuilder mStringBuilder = new StringBuilder();
400        private final String mUnknownAlbum;
401        private final String mUnknownArtist;
402        private final String mAlbumSongSeparator;
403        private final Object[] mFormatArgs = new Object[1];
404
405        class ViewHolder {
406            TextView line1;
407            TextView line2;
408            TextView duration;
409            ImageView play_indicator;
410            ImageView icon;
411        }
412
413        AlbumListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
414            super(context, layout, cursor, from, to);
415
416            mUnknownAlbum = context.getString(R.string.unknown_album_name);
417            mUnknownArtist = context.getString(R.string.unknown_artist_name);
418            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
419
420            Resources r = getResources();
421            mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
422
423            Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
424            mDefaultAlbumIcon = new BitmapDrawable(b);
425            // no filter or dither, it's a lot faster and we can't tell the difference
426            mDefaultAlbumIcon.setFilterBitmap(false);
427            mDefaultAlbumIcon.setDither(false);
428            mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
429            mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
430            mNumSongsIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS);
431            mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
432
433            mResources = context.getResources();
434        }
435
436        @Override
437        public View newView(Context context, Cursor cursor, ViewGroup parent) {
438           View v = super.newView(context, cursor, parent);
439           ViewHolder vh = new ViewHolder();
440           vh.line1 = (TextView) v.findViewById(R.id.line1);
441           vh.line2 = (TextView) v.findViewById(R.id.line2);
442           vh.duration = (TextView) v.findViewById(R.id.duration);
443           vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
444           vh.icon = (ImageView) v.findViewById(R.id.icon);
445           vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
446           vh.icon.setPadding(1, 1, 1, 1);
447           v.setTag(vh);
448           return v;
449        }
450
451        @Override
452        public void bindView(View view, Context context, Cursor cursor) {
453
454            ViewHolder vh = (ViewHolder) view.getTag();
455
456            String name = cursor.getString(mAlbumIdx);
457            String displayname = name;
458            if (MediaFile.UNKNOWN_STRING.equals(name)) {
459                displayname = mUnknownAlbum;
460            }
461            vh.line1.setText(displayname);
462
463            name = cursor.getString(mArtistIdx);
464            displayname = name;
465            if (MediaFile.UNKNOWN_STRING.equals(name)) {
466                displayname = mUnknownArtist;
467            }
468            StringBuilder builder = mStringBuilder;
469            builder.delete(0, builder.length());
470            builder.append(displayname);
471            builder.append(mAlbumSongSeparator);
472
473            int numsongs = cursor.getInt(mNumSongsIdx);
474            if (numsongs == 1) {
475                builder.append(context.getString(R.string.onesong));
476            } else {
477                final Object[] args = mFormatArgs;
478                args[0] = numsongs;
479                builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
480            }
481            vh.line2.setText(builder.toString());
482
483            ImageView iv = vh.icon;
484            // We don't actually need the path to the thumbnail file,
485            // we just use it to see if there is album art or not
486            String art = cursor.getString(mAlbumArtIndex);
487            if (art == null || art.length() == 0) {
488                iv.setImageDrawable(null);
489            } else {
490                int artIndex = cursor.getInt(0);
491                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
492                iv.setImageDrawable(d);
493            }
494
495            int currentalbumid = MusicUtils.getCurrentAlbumId();
496            int aid = cursor.getInt(0);
497            iv = vh.play_indicator;
498            if (currentalbumid == aid) {
499                iv.setImageDrawable(mNowPlayingOverlay);
500            } else {
501                iv.setImageDrawable(null);
502            }
503        }
504        @Override
505        public void changeCursor(Cursor cursor) {
506            super.changeCursor(cursor);
507            mAlbumCursor = cursor;
508        }
509        @Override
510        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
511            return getAlbumCursor(constraint.toString());
512        }
513    }
514
515    private Cursor mAlbumCursor;
516    private String mArtistId;
517    private boolean mHasHeader = false;
518}
519
520