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