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         if (getListView() != null) {
    214             setListAdapter(null);
    215         }
    216         mAdapter = null;
    217         super.onDestroy();
    218     }
    219 
    220     /*
    221      * This listener gets called when the media scanner starts up, and when the
    222      * sd card is unmounted.
    223      */
    224     private BroadcastReceiver mScanListener = new BroadcastReceiver() {
    225         @Override
    226         public void onReceive(Context context, Intent intent) {
    227             MusicUtils.setSpinnerState(QueryBrowserActivity.this);
    228             mReScanHandler.sendEmptyMessage(0);
    229         }
    230     };
    231 
    232     private Handler mReScanHandler = new Handler() {
    233         @Override
    234         public void handleMessage(Message msg) {
    235             if (mAdapter != null) {
    236                 getQueryCursor(mAdapter.getQueryHandler(), null);
    237             }
    238             // if the query results in a null cursor, onQueryComplete() will
    239             // call init(), which will post a delayed message to this handler
    240             // in order to try again.
    241         }
    242     };
    243 
    244     @Override
    245     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    246         switch (requestCode) {
    247             case SCAN_DONE:
    248                 if (resultCode == RESULT_CANCELED) {
    249                     finish();
    250                 } else {
    251                     getQueryCursor(mAdapter.getQueryHandler(), null);
    252                 }
    253                 break;
    254         }
    255     }
    256 
    257     public void init(Cursor c) {
    258 
    259         if (mAdapter == null) {
    260             return;
    261         }
    262         mAdapter.changeCursor(c);
    263 
    264         if (mQueryCursor == null) {
    265             MusicUtils.displayDatabaseError(this);
    266             setListAdapter(null);
    267             mReScanHandler.sendEmptyMessageDelayed(0, 1000);
    268             return;
    269         }
    270         MusicUtils.hideDatabaseError(this);
    271     }
    272 
    273     @Override
    274     protected void onListItemClick(ListView l, View v, int position, long id)
    275     {
    276         // Dialog doesn't allow us to wait for a result, so we need to store
    277         // the info we need for when the dialog posts its result
    278         mQueryCursor.moveToPosition(position);
    279         if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
    280             return;
    281         }
    282         String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
    283                 MediaStore.Audio.Media.MIME_TYPE));
    284 
    285         if ("artist".equals(selectedType)) {
    286             Intent intent = new Intent(Intent.ACTION_PICK);
    287             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    288             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
    289             intent.putExtra("artist", Long.valueOf(id).toString());
    290             startActivity(intent);
    291         } else if ("album".equals(selectedType)) {
    292             Intent intent = new Intent(Intent.ACTION_PICK);
    293             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    294             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
    295             intent.putExtra("album", Long.valueOf(id).toString());
    296             startActivity(intent);
    297         } else if (position >= 0 && id >= 0){
    298             long [] list = new long[] { id };
    299             MusicUtils.playAll(this, list, 0);
    300         } else {
    301             Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
    302         }
    303     }
    304 
    305     @Override
    306     public boolean onOptionsItemSelected(MenuItem item) {
    307         switch (item.getItemId()) {
    308             case USE_AS_RINGTONE: {
    309                 // Set the system setting to make this the current ringtone
    310                 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
    311                 return true;
    312             }
    313 
    314         }
    315         return super.onOptionsItemSelected(item);
    316     }
    317 
    318     private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
    319         if (filter == null) {
    320             filter = "";
    321         }
    322         String[] ccols = new String[] {
    323                 BaseColumns._ID,   // this will be the artist, album or track ID
    324                 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
    325                 MediaStore.Audio.Artists.ARTIST,
    326                 MediaStore.Audio.Albums.ALBUM,
    327                 MediaStore.Audio.Media.TITLE,
    328                 "data1",
    329                 "data2"
    330         };
    331 
    332         Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
    333                 Uri.encode(filter));
    334 
    335         Cursor ret = null;
    336         if (async != null) {
    337             async.startQuery(0, null, search, ccols, null, null, null);
    338         } else {
    339             ret = MusicUtils.query(this, search, ccols, null, null, null);
    340         }
    341         return ret;
    342     }
    343 
    344     static class QueryListAdapter extends SimpleCursorAdapter {
    345         private QueryBrowserActivity mActivity = null;
    346         private AsyncQueryHandler mQueryHandler;
    347         private String mConstraint = null;
    348         private boolean mConstraintIsValid = false;
    349 
    350         class QueryHandler extends AsyncQueryHandler {
    351             QueryHandler(ContentResolver res) {
    352                 super(res);
    353             }
    354 
    355             @Override
    356             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    357                 mActivity.init(cursor);
    358             }
    359         }
    360 
    361         QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
    362                 int layout, Cursor cursor, String[] from, int[] to) {
    363             super(context, layout, cursor, from, to);
    364             mActivity = currentactivity;
    365             mQueryHandler = new QueryHandler(context.getContentResolver());
    366         }
    367 
    368         public void setActivity(QueryBrowserActivity newactivity) {
    369             mActivity = newactivity;
    370         }
    371 
    372         public AsyncQueryHandler getQueryHandler() {
    373             return mQueryHandler;
    374         }
    375 
    376         @Override
    377         public void bindView(View view, Context context, Cursor cursor) {
    378 
    379             TextView tv1 = (TextView) view.findViewById(R.id.line1);
    380             TextView tv2 = (TextView) view.findViewById(R.id.line2);
    381             ImageView iv = (ImageView) view.findViewById(R.id.icon);
    382             ViewGroup.LayoutParams p = iv.getLayoutParams();
    383             if (p == null) {
    384                 // seen this happen, not sure why
    385                 DatabaseUtils.dumpCursor(cursor);
    386                 return;
    387             }
    388             p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    389             p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    390 
    391             String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
    392                     MediaStore.Audio.Media.MIME_TYPE));
    393 
    394             if (mimetype == null) {
    395                 mimetype = "audio/";
    396             }
    397             if (mimetype.equals("artist")) {
    398                 iv.setImageResource(R.drawable.ic_mp_artist_list);
    399                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
    400                         MediaStore.Audio.Artists.ARTIST));
    401                 String displayname = name;
    402                 boolean isunknown = false;
    403                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    404                     displayname = context.getString(R.string.unknown_artist_name);
    405                     isunknown = true;
    406                 }
    407                 tv1.setText(displayname);
    408 
    409                 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
    410                 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
    411 
    412                 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
    413                         numalbums, numsongs, isunknown);
    414 
    415                 tv2.setText(songs_albums);
    416 
    417             } else if (mimetype.equals("album")) {
    418                 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
    419                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
    420                         MediaStore.Audio.Albums.ALBUM));
    421                 String displayname = name;
    422                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    423                     displayname = context.getString(R.string.unknown_album_name);
    424                 }
    425                 tv1.setText(displayname);
    426 
    427                 name = cursor.getString(cursor.getColumnIndexOrThrow(
    428                         MediaStore.Audio.Artists.ARTIST));
    429                 displayname = name;
    430                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    431                     displayname = context.getString(R.string.unknown_artist_name);
    432                 }
    433                 tv2.setText(displayname);
    434 
    435             } else if(mimetype.startsWith("audio/") ||
    436                     mimetype.equals("application/ogg") ||
    437                     mimetype.equals("application/x-ogg")) {
    438                 iv.setImageResource(R.drawable.ic_mp_song_list);
    439                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
    440                         MediaStore.Audio.Media.TITLE));
    441                 tv1.setText(name);
    442 
    443                 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
    444                         MediaStore.Audio.Artists.ARTIST));
    445                 if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) {
    446                     displayname = context.getString(R.string.unknown_artist_name);
    447                 }
    448                 name = cursor.getString(cursor.getColumnIndexOrThrow(
    449                         MediaStore.Audio.Albums.ALBUM));
    450                 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
    451                     name = context.getString(R.string.unknown_album_name);
    452                 }
    453                 tv2.setText(displayname + " - " + name);
    454             }
    455         }
    456         @Override
    457         public void changeCursor(Cursor cursor) {
    458             if (mActivity.isFinishing() && cursor != null) {
    459                 cursor.close();
    460                 cursor = null;
    461             }
    462             if (cursor != mActivity.mQueryCursor) {
    463                 mActivity.mQueryCursor = cursor;
    464                 super.changeCursor(cursor);
    465             }
    466         }
    467         @Override
    468         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    469             String s = constraint.toString();
    470             if (mConstraintIsValid && (
    471                     (s == null && mConstraint == null) ||
    472                     (s != null && s.equals(mConstraint)))) {
    473                 return getCursor();
    474             }
    475             Cursor c = mActivity.getQueryCursor(null, s);
    476             mConstraint = s;
    477             mConstraintIsValid = true;
    478             return c;
    479         }
    480     }
    481 
    482     private ListView mTrackList;
    483     private Cursor mQueryCursor;
    484 }
    485 
    486