Home | History | Annotate | Download | only in music
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.music;
     18 
     19 import com.android.music.MusicUtils.ServiceToken;
     20 
     21 import android.app.ListActivity;
     22 import android.app.SearchManager;
     23 import android.content.AsyncQueryHandler;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ComponentName;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.ServiceConnection;
     31 
     32 import android.database.Cursor;
     33 import android.database.DatabaseUtils;
     34 import android.media.AudioManager;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.os.IBinder;
     39 import android.os.Message;
     40 import android.provider.BaseColumns;
     41 import android.provider.MediaStore;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import android.view.KeyEvent;
     45 import android.view.MenuItem;
     46 import android.view.View;
     47 import android.view.ViewGroup;
     48 import android.view.Window;
     49 import android.view.ViewGroup.OnHierarchyChangeListener;
     50 import android.widget.ImageView;
     51 import android.widget.ListView;
     52 import android.widget.SimpleCursorAdapter;
     53 import android.widget.TextView;
     54 
     55 import java.util.ArrayList;
     56 
     57 public class QueryBrowserActivity
     58         extends ListActivity implements MusicUtils.Defs, ServiceConnection {
     59     private final static int PLAY_NOW = 0;
     60     private final static int ADD_TO_QUEUE = 1;
     61     private final static int PLAY_NEXT = 2;
     62     private final static int PLAY_ARTIST = 3;
     63     private final static int EXPLORE_ARTIST = 4;
     64     private final static int PLAY_ALBUM = 5;
     65     private final static int EXPLORE_ALBUM = 6;
     66     private final static int REQUERY = 3;
     67     private QueryListAdapter mAdapter;
     68     private boolean mAdapterSent;
     69     private String mFilterString = "";
     70     private ServiceToken mToken;
     71 
     72     public QueryBrowserActivity() {}
     73 
     74     /** Called when the activity is first created. */
     75     @Override
     76     public void onCreate(Bundle icicle) {
     77         super.onCreate(icicle);
     78         setVolumeControlStream(AudioManager.STREAM_MUSIC);
     79         mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
     80         mToken = MusicUtils.bindToService(this, this);
     81         // defer the real work until we're bound to the service
     82     }
     83 
     84     public void onServiceConnected(ComponentName name, IBinder service) {
     85         IntentFilter f = new IntentFilter();
     86         f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
     87         f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
     88         f.addDataScheme("file");
     89         registerReceiver(mScanListener, f);
     90 
     91         Intent intent = getIntent();
     92         String action = intent != null ? intent.getAction() : null;
     93 
     94         if (Intent.ACTION_VIEW.equals(action)) {
     95             // this is something we got from the search bar
     96             Uri uri = intent.getData();
     97             String path = uri.toString();
     98             if (path.startsWith("content://media/external/audio/media/")) {
     99                 // This is a specific file
    100                 String id = uri.getLastPathSegment();
    101                 long[] list = new long[] {Long.valueOf(id)};
    102                 MusicUtils.playAll(this, list, 0);
    103                 finish();
    104                 return;
    105             } else if (path.startsWith("content://media/external/audio/albums/")) {
    106                 // This is an album, show the songs on it
    107                 Intent i = new Intent(Intent.ACTION_PICK);
    108                 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
    109                 i.putExtra("album", uri.getLastPathSegment());
    110                 startActivity(i);
    111                 finish();
    112                 return;
    113             } else if (path.startsWith("content://media/external/audio/artists/")) {
    114                 // This is an artist, show the albums for that artist
    115                 Intent i = new Intent(Intent.ACTION_PICK);
    116                 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
    117                 i.putExtra("artist", uri.getLastPathSegment());
    118                 startActivity(i);
    119                 finish();
    120                 return;
    121             }
    122         }
    123 
    124         mFilterString = intent.getStringExtra(SearchManager.QUERY);
    125         if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) {
    126             String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
    127             String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
    128             String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
    129             String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
    130             if (focus != null) {
    131                 if (focus.startsWith("audio/") && title != null) {
    132                     mFilterString = title;
    133                 } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
    134                     if (album != null) {
    135                         mFilterString = album;
    136                         if (artist != null) {
    137                             mFilterString = mFilterString + " " + artist;
    138                         }
    139                     }
    140                 } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
    141                     if (artist != null) {
    142                         mFilterString = artist;
    143                     }
    144                 }
    145             }
    146         }
    147 
    148         setContentView(R.layout.query_activity);
    149         mTrackList = getListView();
    150         mTrackList.setTextFilterEnabled(true);
    151         if (mAdapter == null) {
    152             mAdapter = new QueryListAdapter(getApplication(), this, R.layout.track_list_item,
    153                     null, // cursor
    154                     new String[] {}, new int[] {});
    155             setListAdapter(mAdapter);
    156             if (TextUtils.isEmpty(mFilterString)) {
    157                 getQueryCursor(mAdapter.getQueryHandler(), null);
    158             } else {
    159                 mTrackList.setFilterText(mFilterString);
    160                 mFilterString = null;
    161             }
    162         } else {
    163             mAdapter.setActivity(this);
    164             setListAdapter(mAdapter);
    165             mQueryCursor = mAdapter.getCursor();
    166             if (mQueryCursor != null) {
    167                 init(mQueryCursor);
    168             } else {
    169                 getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
    170             }
    171         }
    172     }
    173 
    174     public void onServiceDisconnected(ComponentName name) {}
    175 
    176     @Override
    177     public Object onRetainNonConfigurationInstance() {
    178         mAdapterSent = true;
    179         return mAdapter;
    180     }
    181 
    182     @Override
    183     public void onPause() {
    184         mReScanHandler.removeCallbacksAndMessages(null);
    185         super.onPause();
    186     }
    187 
    188     @Override
    189     public void onDestroy() {
    190         MusicUtils.unbindFromService(mToken);
    191         unregisterReceiver(mScanListener);
    192         // If we have an adapter and didn't send it off to another activity yet, we should
    193         // close its cursor, which we do by assigning a null cursor to it. Doing this
    194         // instead of closing the cursor directly keeps the framework from accessing
    195         // the closed cursor later.
    196         if (!mAdapterSent && mAdapter != null) {
    197             mAdapter.changeCursor(null);
    198         }
    199         // Because we pass the adapter to the next activity, we need to make
    200         // sure it doesn't keep a reference to this activity. We can do this
    201         // by clearing its DatasetObservers, which setListAdapter(null) does.
    202         if (getListView() != null) {
    203             setListAdapter(null);
    204         }
    205         mAdapter = null;
    206         super.onDestroy();
    207     }
    208 
    209     /*
    210      * This listener gets called when the media scanner starts up, and when the
    211      * sd card is unmounted.
    212      */
    213     private BroadcastReceiver mScanListener = new BroadcastReceiver() {
    214         @Override
    215         public void onReceive(Context context, Intent intent) {
    216             MusicUtils.setSpinnerState(QueryBrowserActivity.this);
    217             mReScanHandler.sendEmptyMessage(0);
    218         }
    219     };
    220 
    221     private Handler mReScanHandler = new Handler() {
    222         @Override
    223         public void handleMessage(Message msg) {
    224             if (mAdapter != null) {
    225                 getQueryCursor(mAdapter.getQueryHandler(), null);
    226             }
    227             // if the query results in a null cursor, onQueryComplete() will
    228             // call init(), which will post a delayed message to this handler
    229             // in order to try again.
    230         }
    231     };
    232 
    233     @Override
    234     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    235         switch (requestCode) {
    236             case SCAN_DONE:
    237                 if (resultCode == RESULT_CANCELED) {
    238                     finish();
    239                 } else {
    240                     getQueryCursor(mAdapter.getQueryHandler(), null);
    241                 }
    242                 break;
    243         }
    244     }
    245 
    246     public void init(Cursor c) {
    247         if (mAdapter == null) {
    248             return;
    249         }
    250         mAdapter.changeCursor(c);
    251 
    252         if (mQueryCursor == null) {
    253             MusicUtils.displayDatabaseError(this);
    254             setListAdapter(null);
    255             mReScanHandler.sendEmptyMessageDelayed(0, 1000);
    256             return;
    257         }
    258         MusicUtils.hideDatabaseError(this);
    259     }
    260 
    261     @Override
    262     protected void onListItemClick(ListView l, View v, int position, long id) {
    263         // Dialog doesn't allow us to wait for a result, so we need to store
    264         // the info we need for when the dialog posts its result
    265         mQueryCursor.moveToPosition(position);
    266         if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
    267             return;
    268         }
    269         String selectedType = mQueryCursor.getString(
    270                 mQueryCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
    271 
    272         if ("artist".equals(selectedType)) {
    273             Intent intent = new Intent(Intent.ACTION_PICK);
    274             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    275             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
    276             intent.putExtra("artist", Long.valueOf(id).toString());
    277             startActivity(intent);
    278         } else if ("album".equals(selectedType)) {
    279             Intent intent = new Intent(Intent.ACTION_PICK);
    280             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    281             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
    282             intent.putExtra("album", Long.valueOf(id).toString());
    283             startActivity(intent);
    284         } else if (position >= 0 && id >= 0) {
    285             long[] list = new long[] {id};
    286             MusicUtils.playAll(this, list, 0);
    287         } else {
    288             Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
    289         }
    290     }
    291 
    292     @Override
    293     public boolean onOptionsItemSelected(MenuItem item) {
    294         switch (item.getItemId()) {
    295             case USE_AS_RINGTONE: {
    296                 // Set the system setting to make this the current ringtone
    297                 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
    298                 return true;
    299             }
    300         }
    301         return super.onOptionsItemSelected(item);
    302     }
    303 
    304     private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
    305         if (filter == null) {
    306             filter = "";
    307         }
    308         String[] ccols = new String[] {
    309                 BaseColumns._ID, // this will be the artist, album or track ID
    310                 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
    311                 MediaStore.Audio.Artists.ARTIST, MediaStore.Audio.Albums.ALBUM,
    312                 MediaStore.Audio.Media.TITLE, "data1", "data2"};
    313 
    314         Uri search = Uri.parse("content://media/external/audio/search/fancy/" + Uri.encode(filter));
    315 
    316         Cursor ret = null;
    317         if (async != null) {
    318             async.startQuery(0, null, search, ccols, null, null, null);
    319         } else {
    320             ret = MusicUtils.query(this, search, ccols, null, null, null);
    321         }
    322         return ret;
    323     }
    324 
    325     static class QueryListAdapter extends SimpleCursorAdapter {
    326         private QueryBrowserActivity mActivity = null;
    327         private AsyncQueryHandler mQueryHandler;
    328         private String mConstraint = null;
    329         private boolean mConstraintIsValid = false;
    330 
    331         class QueryHandler extends AsyncQueryHandler {
    332             QueryHandler(ContentResolver res) {
    333                 super(res);
    334             }
    335 
    336             @Override
    337             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    338                 mActivity.init(cursor);
    339             }
    340         }
    341 
    342         QueryListAdapter(Context context, QueryBrowserActivity currentactivity, int layout,
    343                 Cursor cursor, String[] from, int[] to) {
    344             super(context, layout, cursor, from, to);
    345             mActivity = currentactivity;
    346             mQueryHandler = new QueryHandler(context.getContentResolver());
    347         }
    348 
    349         public void setActivity(QueryBrowserActivity newactivity) {
    350             mActivity = newactivity;
    351         }
    352 
    353         public AsyncQueryHandler getQueryHandler() {
    354             return mQueryHandler;
    355         }
    356 
    357         @Override
    358         public void bindView(View view, Context context, Cursor cursor) {
    359             TextView tv1 = (TextView) view.findViewById(R.id.line1);
    360             TextView tv2 = (TextView) view.findViewById(R.id.line2);
    361             ImageView iv = (ImageView) view.findViewById(R.id.icon);
    362             ViewGroup.LayoutParams p = iv.getLayoutParams();
    363             if (p == null) {
    364                 // seen this happen, not sure why
    365                 DatabaseUtils.dumpCursor(cursor);
    366                 return;
    367             }
    368             p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    369             p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    370 
    371             String mimetype = cursor.getString(
    372                     cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
    373 
    374             if (mimetype == null) {
    375                 mimetype = "audio/";
    376             }
    377             if (mimetype.equals("artist")) {
    378                 iv.setImageResource(R.drawable.ic_mp_artist_list);
    379                 String name = cursor.getString(
    380                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    381                 String displayname = name;
    382                 boolean isunknown = false;
    383                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    384                     displayname = context.getString(R.string.unknown_artist_name);
    385                     isunknown = true;
    386                 }
    387                 tv1.setText(displayname);
    388 
    389                 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
    390                 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
    391 
    392                 String songs_albums =
    393                         MusicUtils.makeAlbumsSongsLabel(context, numalbums, numsongs, isunknown);
    394 
    395                 tv2.setText(songs_albums);
    396 
    397             } else if (mimetype.equals("album")) {
    398                 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
    399                 String name = cursor.getString(
    400                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
    401                 String displayname = name;
    402                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    403                     displayname = context.getString(R.string.unknown_album_name);
    404                 }
    405                 tv1.setText(displayname);
    406 
    407                 name = cursor.getString(
    408                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    409                 displayname = name;
    410                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    411                     displayname = context.getString(R.string.unknown_artist_name);
    412                 }
    413                 tv2.setText(displayname);
    414 
    415             } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
    416                     || mimetype.equals("application/x-ogg")) {
    417                 iv.setImageResource(R.drawable.ic_mp_song_list);
    418                 String name = cursor.getString(
    419                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
    420                 tv1.setText(name);
    421 
    422                 String displayname = cursor.getString(
    423                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    424                 if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) {
    425                     displayname = context.getString(R.string.unknown_artist_name);
    426                 }
    427                 name = cursor.getString(
    428                         cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
    429                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    430                     name = context.getString(R.string.unknown_album_name);
    431                 }
    432                 tv2.setText(displayname + " - " + name);
    433             }
    434         }
    435         @Override
    436         public void changeCursor(Cursor cursor) {
    437             if (mActivity.isFinishing() && cursor != null) {
    438                 cursor.close();
    439                 cursor = null;
    440             }
    441             if (cursor != mActivity.mQueryCursor) {
    442                 mActivity.mQueryCursor = cursor;
    443                 super.changeCursor(cursor);
    444             }
    445         }
    446         @Override
    447         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    448             String s = constraint.toString();
    449             if (mConstraintIsValid && ((s == null && mConstraint == null)
    450                                               || (s != null && s.equals(mConstraint)))) {
    451                 return getCursor();
    452             }
    453             Cursor c = mActivity.getQueryCursor(null, s);
    454             mConstraint = s;
    455             mConstraintIsValid = true;
    456             return c;
    457         }
    458     }
    459 
    460     private ListView mTrackList;
    461     private Cursor mQueryCursor;
    462 }
    463