• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
QueryBrowserActivity()73     public QueryBrowserActivity()
74     {
75     }
76 
77     /** Called when the activity is first created. */
78     @Override
onCreate(Bundle icicle)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 
onServiceConnected(ComponentName name, IBinder service)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 
onServiceDisconnected(ComponentName name)183     public void onServiceDisconnected(ComponentName name) {
184 
185     }
186 
187     @Override
onRetainNonConfigurationInstance()188     public Object onRetainNonConfigurationInstance() {
189         mAdapterSent = true;
190         return mAdapter;
191     }
192 
193     @Override
onPause()194     public void onPause() {
195         mReScanHandler.removeCallbacksAndMessages(null);
196         super.onPause();
197     }
198 
199     @Override
onDestroy()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
onActivityResult(int requestCode, int resultCode, Intent intent)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 
init(Cursor c)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
onListItemClick(ListView l, View v, int position, long id)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
onOptionsItemSelected(MenuItem item)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 
getQueryCursor(AsyncQueryHandler async, String filter)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 {
QueryHandler(ContentResolver res)349             QueryHandler(ContentResolver res) {
350                 super(res);
351             }
352 
353             @Override
onQueryComplete(int token, Object cookie, Cursor cursor)354             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
355                 mActivity.init(cursor);
356             }
357         }
358 
QueryListAdapter(Context context, QueryBrowserActivity currentactivity, int layout, Cursor cursor, String[] from, int[] to)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 
setActivity(QueryBrowserActivity newactivity)366         public void setActivity(QueryBrowserActivity newactivity) {
367             mActivity = newactivity;
368         }
369 
getQueryHandler()370         public AsyncQueryHandler getQueryHandler() {
371             return mQueryHandler;
372         }
373 
374         @Override
bindView(View view, Context context, Cursor cursor)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
changeCursor(Cursor cursor)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
runQueryOnBackgroundThread(CharSequence constraint)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