OSDN Git Service

Code drop from //branches/cupcake/...@124589
[android-x86/packages-apps-Music.git] / src / com / android / music / AlbumBrowserActivity.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.music;
18
19 import android.app.ListActivity;
20 import android.app.SearchManager;
21 import android.content.AsyncQueryHandler;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.media.AudioManager;
34 import android.media.MediaFile;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Message;
39 import android.provider.MediaStore;
40 import android.view.ContextMenu;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.SubMenu;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.Window;
47 import android.view.ContextMenu.ContextMenuInfo;
48 import android.widget.Adapter;
49 import android.widget.AlphabetIndexer;
50 import android.widget.CursorAdapter;
51 import android.widget.ImageView;
52 import android.widget.ListAdapter;
53 import android.widget.ListView;
54 import android.widget.SectionIndexer;
55 import android.widget.SimpleCursorAdapter;
56 import android.widget.TextView;
57 import android.widget.AdapterView.AdapterContextMenuInfo;
58
59 import java.text.Collator;
60
61 public class AlbumBrowserActivity extends ListActivity
62     implements View.OnCreateContextMenuListener, MusicUtils.Defs
63 {
64     private String mCurrentAlbumId;
65     private String mCurrentAlbumName;
66     private String mCurrentArtistNameForAlbum;
67     private MyQueryHandler mQueryHandler;
68     private String mFilterString = "";
69     private final static int SEARCH = CHILD_MENU_BASE;
70
71     public AlbumBrowserActivity()
72     {
73     }
74
75     /** Called when the activity is first created. */
76     @Override
77     public void onCreate(Bundle icicle)
78     {
79         if (icicle != null) {
80             mCurrentAlbumId = icicle.getString("selectedalbum");
81             mArtistId = icicle.getString("artist");
82         } else {
83             mArtistId = getIntent().getStringExtra("artist");
84         }
85         super.onCreate(icicle);
86         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
87         setVolumeControlStream(AudioManager.STREAM_MUSIC);
88         MusicUtils.bindToService(this);
89
90         IntentFilter f = new IntentFilter();
91         f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
92         f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
93         f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
94         f.addDataScheme("file");
95         registerReceiver(mScanListener, f);
96
97         mQueryHandler = new MyQueryHandler(getContentResolver());
98         mAlbumCursor = (Cursor) getLastNonConfigurationInstance();
99         if (mAlbumCursor == null) {
100             //Log.i("@@@", "starting query");
101             setTitle(R.string.working_albums);
102             getAlbumCursor(mQueryHandler);
103         } else {
104             init(mAlbumCursor);
105             setTitle();
106         }
107     }
108
109     @Override
110     public Object onRetainNonConfigurationInstance() {
111         Cursor c = mAlbumCursor;
112         mAlbumCursor = null;
113         return c;
114     }
115     
116     class MyQueryHandler extends AsyncQueryHandler {
117         MyQueryHandler(ContentResolver res) {
118             super(res);
119         }
120         
121         @Override
122         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
123             //Log.i("@@@", "query complete");
124             init(cursor);
125             setTitle();
126         }
127     }
128
129     @Override
130     public void onSaveInstanceState(Bundle outcicle) {
131         // need to store the selected item so we don't lose it in case
132         // of an orientation switch. Otherwise we could lose it while
133         // in the middle of specifying a playlist to add the item to.
134         outcicle.putString("selectedalbum", mCurrentAlbumId);
135         outcicle.putString("artist", mArtistId);
136         super.onSaveInstanceState(outcicle);
137     }
138
139     @Override
140     public void onDestroy() {
141         MusicUtils.unbindFromService(this);
142         if (mAlbumCursor != null) {
143             mAlbumCursor.close();
144             mAlbumCursor = null;
145         }
146         unregisterReceiver(mScanListener);
147         super.onDestroy();
148     }
149     
150     @Override
151     public void onResume() {
152         super.onResume();
153         IntentFilter f = new IntentFilter();
154         f.addAction(MediaPlaybackService.META_CHANGED);
155         f.addAction(MediaPlaybackService.QUEUE_CHANGED);
156         registerReceiver(mTrackListListener, f);
157         mTrackListListener.onReceive(null, null);
158
159         MusicUtils.setSpinnerState(this);
160     }
161
162     private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
163         @Override
164         public void onReceive(Context context, Intent intent) {
165             getListView().invalidateViews();
166         }
167     };
168     private BroadcastReceiver mScanListener = new BroadcastReceiver() {
169         @Override
170         public void onReceive(Context context, Intent intent) {
171             MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
172             mReScanHandler.sendEmptyMessage(0);
173             if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
174                 MusicUtils.clearAlbumArtCache();
175             }
176         }
177     };
178     
179     private Handler mReScanHandler = new Handler() {
180         @Override
181         public void handleMessage(Message msg) {
182             setTitle();
183             getAlbumCursor(mQueryHandler);
184             if (mAlbumCursor == null) {
185                 sendEmptyMessageDelayed(0, 1000);
186             }
187         }
188     };
189
190     @Override
191     public void onPause() {
192         unregisterReceiver(mTrackListListener);
193         mReScanHandler.removeCallbacksAndMessages(null);
194         super.onPause();
195     }
196
197     public void init(Cursor c) {
198
199         mAlbumCursor = c;
200
201         /* The UI spec does not call for icons in the title bar
202         if (mArtistId != null) {
203             requestWindowFeature(Window.FEATURE_LEFT_ICON);
204         }
205         */
206         setContentView(R.layout.media_picker_activity);
207         /* The UI spec does not call for icons in the title bar
208         if (mArtistId != null) {
209             // this call needs to happen after setContentView
210             setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.person_title_icon);
211         }
212         */
213
214         if (mAlbumCursor == null) {
215             MusicUtils.displayDatabaseError(this);
216             return;
217         }
218
219
220         // Map Cursor columns to views defined in media_list_item.xml
221         CursorAdapter adapter = (CursorAdapter) getListAdapter();
222         if (adapter == null) {
223             adapter = new AlbumListAdapter(
224                     this,
225                     R.layout.track_list_item,
226                     mAlbumCursor,
227                     new String[] {},
228                     new int[] {});
229             setListAdapter(adapter);
230         } else {
231             adapter.changeCursor(mAlbumCursor);
232         }
233         ListView lv = getListView();
234         lv.setOnCreateContextMenuListener(this);
235         lv.setTextFilterEnabled(true);
236     }
237
238     private void setTitle() {
239         CharSequence fancyName = "";
240         if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
241             mAlbumCursor.moveToFirst();
242             fancyName = mAlbumCursor.getString(3);
243             if (MediaFile.UNKNOWN_STRING.equals(fancyName))
244                 fancyName = getText(R.string.unknown_artist_name);
245
246             if (mArtistId != null && fancyName != null)
247                 setTitle(fancyName);
248             else
249                 setTitle(R.string.albums_title);
250         } else {
251             setTitle(R.string.no_albums_title);
252         }
253     }
254     
255     @Override
256     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
257         menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
258         SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
259         MusicUtils.makePlaylistMenu(this, sub);
260         menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
261         menu.add(0, SEARCH, 0, R.string.search_title);
262
263         AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
264         mAlbumCursor.moveToPosition(mi.position);
265         mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
266         mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
267         mCurrentArtistNameForAlbum = mAlbumCursor.getString(
268                 mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
269         menu.setHeaderTitle(mCurrentAlbumName);
270     }
271
272     @Override
273     public boolean onContextItemSelected(MenuItem item) {
274         switch (item.getItemId()) {
275             case PLAY_SELECTION: {
276                 // play the selected album
277                 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
278                 MusicUtils.playAll(this, list, 0);
279                 return true;
280             }
281
282             case QUEUE: {
283                 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
284                 MusicUtils.addToCurrentPlaylist(this, list);
285                 return true;
286             }
287
288             case NEW_PLAYLIST: {
289                 Intent intent = new Intent();
290                 intent.setClass(this, CreatePlaylist.class);
291                 startActivityForResult(intent, NEW_PLAYLIST);
292                 return true;
293             }
294
295             case PLAYLIST_SELECTED: {
296                 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
297                 int playlist = item.getIntent().getIntExtra("playlist", 0);
298                 MusicUtils.addToPlaylist(this, list, playlist);
299                 return true;
300             }
301             case DELETE_ITEM: {
302                 int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
303                 String f = getString(R.string.delete_album_desc); 
304                 String desc = String.format(f, mCurrentAlbumName);
305                 Bundle b = new Bundle();
306                 b.putString("description", desc);
307                 b.putIntArray("items", list);
308                 Intent intent = new Intent();
309                 intent.setClass(this, DeleteItems.class);
310                 intent.putExtras(b);
311                 startActivityForResult(intent, -1);
312                 return true;
313             }
314             case SEARCH:
315                 doSearch();
316                 return true;
317
318         }
319         return super.onContextItemSelected(item);
320     }
321
322     void doSearch() {
323         CharSequence title = null;
324         String query = null;
325         
326         Intent i = new Intent();
327         i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
328         
329         title = mCurrentAlbumName;
330         query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
331         i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
332         i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
333         i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
334         title = getString(R.string.mediasearch, title);
335         i.putExtra(SearchManager.QUERY, query);
336
337         startActivity(Intent.createChooser(i, title));
338     }
339
340     @Override
341     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
342         switch (requestCode) {
343             case SCAN_DONE:
344                 if (resultCode == RESULT_CANCELED) {
345                     finish();
346                 } else {
347                     getAlbumCursor(mQueryHandler);
348                 }
349                 break;
350
351             case NEW_PLAYLIST:
352                 if (resultCode == RESULT_OK) {
353                     Uri uri = intent.getData();
354                     if (uri != null) {
355                         int [] list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
356                         MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
357                     }
358                 }
359                 break;
360         }
361     }
362
363     @Override
364     protected void onListItemClick(ListView l, View v, int position, long id)
365     {
366         if (mHasHeader) {
367             position --;
368         }
369         Intent intent = new Intent(Intent.ACTION_PICK);
370         intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
371         intent.putExtra("album", Long.valueOf(id).toString());
372         intent.putExtra("artist", mArtistId);
373         startActivity(intent);
374     }
375
376     @Override
377     public boolean onCreateOptionsMenu(Menu menu) {
378         super.onCreateOptionsMenu(menu);
379         menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
380         menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
381         menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
382         return true;
383     }
384
385     @Override
386     public boolean onPrepareOptionsMenu(Menu menu) {
387         menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
388         return super.onPrepareOptionsMenu(menu);
389     }
390
391     @Override
392     public boolean onOptionsItemSelected(MenuItem item) {
393         Intent intent;
394         Cursor cursor;
395         switch (item.getItemId()) {
396             case GOTO_START:
397                 intent = new Intent();
398                 intent.setClass(this, MusicBrowserActivity.class);
399                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
400                 startActivity(intent);
401                 return true;
402
403             case GOTO_PLAYBACK:
404                 intent = new Intent("com.android.music.PLAYBACK_VIEWER");
405                 startActivity(intent);
406                 return true;
407
408             case SHUFFLE_ALL:
409                 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
410                         new String [] { MediaStore.Audio.Media._ID},
411                         MediaStore.Audio.Media.IS_MUSIC + "=1", null,
412                         MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
413                 if (cursor != null) {
414                     MusicUtils.shuffleAll(this, cursor);
415                     cursor.close();
416                 }
417                 return true;
418         }
419         return super.onOptionsItemSelected(item);
420     }
421
422     private Cursor getAlbumCursor(AsyncQueryHandler async) {
423         StringBuilder where = new StringBuilder();
424         where.append(MediaStore.Audio.Albums.ALBUM + " != ''");
425         
426         // Add in the filtering constraints
427         String [] keywords = null;
428         if (mFilterString != null) {
429             String [] searchWords = mFilterString.split(" ");
430             keywords = new String[searchWords.length];
431             Collator col = Collator.getInstance();
432             col.setStrength(Collator.PRIMARY);
433             for (int i = 0; i < searchWords.length; i++) {
434                 keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
435             }
436             for (int i = 0; i < searchWords.length; i++) {
437                 where.append(" AND ");
438                 where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
439                 where.append(MediaStore.Audio.Media.ALBUM_KEY + " LIKE ?");
440             }
441         }
442
443         String whereclause = where.toString();  
444             
445         String[] cols = new String[] {
446                 MediaStore.Audio.Albums._ID,
447                 MediaStore.Audio.Albums.ALBUM,
448                 MediaStore.Audio.Albums.ALBUM_KEY,
449                 MediaStore.Audio.Albums.ARTIST,
450                 MediaStore.Audio.Albums.NUMBER_OF_SONGS,
451                 MediaStore.Audio.Albums.ALBUM_ART
452         };
453         Cursor ret = null;
454         if (mArtistId != null) {
455             if (async != null) {
456                 async.startQuery(0, null,
457                         MediaStore.Audio.Artists.Albums.getContentUri("external",
458                                 Long.valueOf(mArtistId)),
459                         cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
460             } else {
461                 ret = MusicUtils.query(this,
462                         MediaStore.Audio.Artists.Albums.getContentUri("external",
463                                 Long.valueOf(mArtistId)),
464                         cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
465             }
466         } else {
467             if (async != null) {
468                 async.startQuery(0, null,
469                         MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
470                         cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
471             } else {
472                 ret = MusicUtils.query(this, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
473                         cols, whereclause, keywords, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
474             }
475         }
476         return ret;
477     }
478     
479     class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer {
480         
481         private final Drawable mNowPlayingOverlay;
482         private final BitmapDrawable mDefaultAlbumIcon;
483         private final int mAlbumIdx;
484         private final int mArtistIdx;
485         private final int mNumSongsIdx;
486         private final int mAlbumArtIndex;
487         private final Resources mResources;
488         private final StringBuilder mStringBuilder = new StringBuilder();
489         private final String mUnknownAlbum;
490         private final String mUnknownArtist;
491         private final String mAlbumSongSeparator;
492         private final Object[] mFormatArgs = new Object[1];
493         private final AlphabetIndexer mIndexer;
494         
495         class ViewHolder {
496             TextView line1;
497             TextView line2;
498             TextView duration;
499             ImageView play_indicator;
500             ImageView icon;
501         }
502
503         AlbumListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to) {
504             super(context, layout, cursor, from, to);
505
506             mUnknownAlbum = context.getString(R.string.unknown_album_name);
507             mUnknownArtist = context.getString(R.string.unknown_artist_name);
508             mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
509
510             Resources r = getResources();
511             mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
512
513             Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
514             mDefaultAlbumIcon = new BitmapDrawable(b);
515             // no filter or dither, it's a lot faster and we can't tell the difference
516             mDefaultAlbumIcon.setFilterBitmap(false);
517             mDefaultAlbumIcon.setDither(false);
518             mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
519             mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
520             mNumSongsIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS);
521             mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
522             
523             mResources = context.getResources();
524             
525             mIndexer = new MusicAlphabetIndexer(cursor, mAlbumIdx, mResources.getString(
526                     com.android.internal.R.string.fast_scroll_alphabet));
527         }
528
529         @Override
530         public View newView(Context context, Cursor cursor, ViewGroup parent) {
531            View v = super.newView(context, cursor, parent);
532            ViewHolder vh = new ViewHolder();
533            vh.line1 = (TextView) v.findViewById(R.id.line1);
534            vh.line2 = (TextView) v.findViewById(R.id.line2);
535            vh.duration = (TextView) v.findViewById(R.id.duration);
536            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
537            vh.icon = (ImageView) v.findViewById(R.id.icon);
538            vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
539            vh.icon.setPadding(1, 1, 1, 1);
540            v.setTag(vh);
541            return v;
542         }
543
544         @Override
545         public void bindView(View view, Context context, Cursor cursor) {
546             
547             ViewHolder vh = (ViewHolder) view.getTag();
548
549             String name = cursor.getString(mAlbumIdx);
550             String displayname = name;
551             if (MediaFile.UNKNOWN_STRING.equals(name)) {
552                 displayname = mUnknownAlbum;
553             }
554             vh.line1.setText(displayname);
555             
556             name = cursor.getString(mArtistIdx);
557             displayname = name;
558             if (MediaFile.UNKNOWN_STRING.equals(name)) {
559                 displayname = mUnknownArtist;
560             }
561             StringBuilder builder = mStringBuilder;
562             builder.delete(0, builder.length());
563             builder.append(displayname);
564             builder.append(mAlbumSongSeparator);
565             
566             int numsongs = cursor.getInt(mNumSongsIdx);
567             if (numsongs == 1) {
568                 builder.append(context.getString(R.string.onesong));
569             } else {
570                 final Object[] args = mFormatArgs;
571                 args[0] = numsongs;
572                 builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
573             }
574             vh.line2.setText(builder.toString());
575
576             ImageView iv = vh.icon;
577             // We don't actually need the path to the thumbnail file,
578             // we just use it to see if there is album art or not
579             String art = cursor.getString(mAlbumArtIndex);
580             if (art == null || art.length() == 0) {
581                 iv.setImageDrawable(null);
582             } else {
583                 int artIndex = cursor.getInt(0);
584                 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
585                 iv.setImageDrawable(d);
586             }
587             
588             int currentalbumid = MusicUtils.getCurrentAlbumId();
589             int aid = cursor.getInt(0);
590             iv = vh.play_indicator;
591             if (currentalbumid == aid) {
592                 iv.setImageDrawable(mNowPlayingOverlay);
593             } else {
594                 iv.setImageDrawable(null);
595             }
596         }
597         
598         @Override
599         public void changeCursor(Cursor cursor) {
600             super.changeCursor(cursor);
601             mAlbumCursor = cursor;
602             mIndexer.setCursor(cursor);
603         }
604         
605         @Override
606         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
607             mFilterString = constraint.toString();
608             return getAlbumCursor(null);
609         }
610         
611         public Object[] getSections() {
612             return mIndexer.getSections();
613         }
614         
615         public int getPositionForSection(int section) {
616             return mIndexer.getPositionForSection(section);
617         }
618         
619         public int getSectionForPosition(int position) {
620             return 0;
621         }
622     }
623
624     private Cursor mAlbumCursor;
625     private String mArtistId;
626     private boolean mHasHeader = false;
627 }
628