2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.music;
19 import android.app.ListActivity;
20 import android.app.SearchManager;
21 import android.content.AsyncQueryHandler;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
30 import android.database.Cursor;
31 import android.database.DatabaseUtils;
32 import android.media.AudioManager;
33 import android.media.MediaFile;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.Message;
39 import android.provider.BaseColumns;
40 import android.provider.MediaStore;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyEvent;
44 import android.view.MenuItem;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.Window;
48 import android.view.ViewGroup.OnHierarchyChangeListener;
49 import android.widget.ImageView;
50 import android.widget.ListView;
51 import android.widget.SimpleCursorAdapter;
52 import android.widget.TextView;
54 import java.util.ArrayList;
56 public class QueryBrowserActivity extends ListActivity
57 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 = "";
71 public QueryBrowserActivity()
75 /** Called when the activity is first created. */
77 public void onCreate(Bundle icicle)
79 super.onCreate(icicle);
80 setVolumeControlStream(AudioManager.STREAM_MUSIC);
81 mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
82 MusicUtils.bindToService(this, this);
83 // defer the real work until we're bound to the service
87 public void onServiceConnected(ComponentName name, IBinder service) {
88 IntentFilter f = new IntentFilter();
89 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
90 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
91 f.addDataScheme("file");
92 registerReceiver(mScanListener, f);
94 Intent intent = getIntent();
95 String action = intent != null ? intent.getAction() : null;
97 if (Intent.ACTION_VIEW.equals(action)) {
98 // this is something we got from the search bar
99 Uri uri = intent.getData();
100 String path = uri.toString();
101 if (path.startsWith("content://media/external/audio/media/")) {
102 // This is a specific file
103 String id = uri.getLastPathSegment();
104 long [] list = new long[] { Long.valueOf(id) };
105 MusicUtils.playAll(this, list, 0);
108 } else if (path.startsWith("content://media/external/audio/albums/")) {
109 // This is an album, show the songs on it
110 Intent i = new Intent(Intent.ACTION_PICK);
111 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
112 i.putExtra("album", uri.getLastPathSegment());
116 } else if (path.startsWith("content://media/external/audio/artists/")) {
117 // This is an artist, show the albums for that artist
118 Intent i = new Intent(Intent.ACTION_PICK);
119 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
120 i.putExtra("artist", uri.getLastPathSegment());
127 mFilterString = intent.getStringExtra(SearchManager.QUERY);
128 if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) {
129 String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
130 String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
131 String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
132 String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
134 if (focus.startsWith("audio/") && title != null) {
135 mFilterString = title;
136 } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
138 mFilterString = album;
139 if (artist != null) {
140 mFilterString = mFilterString + " " + artist;
143 } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
144 if (artist != null) {
145 mFilterString = artist;
151 setContentView(R.layout.query_activity);
152 mTrackList = getListView();
153 mTrackList.setTextFilterEnabled(true);
154 if (mAdapter == null) {
155 mAdapter = new QueryListAdapter(
158 R.layout.track_list_item,
162 setListAdapter(mAdapter);
163 if (TextUtils.isEmpty(mFilterString)) {
164 getQueryCursor(mAdapter.getQueryHandler(), null);
166 mTrackList.setFilterText(mFilterString);
167 mFilterString = null;
170 mAdapter.setActivity(this);
171 setListAdapter(mAdapter);
172 mQueryCursor = mAdapter.getCursor();
173 if (mQueryCursor != null) {
176 getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
181 public void onServiceDisconnected(ComponentName name) {
186 public Object onRetainNonConfigurationInstance() {
192 public void onPause() {
193 mReScanHandler.removeCallbacksAndMessages(null);
198 public void onDestroy() {
199 MusicUtils.unbindFromService(this);
200 unregisterReceiver(mScanListener);
201 // If we have an adapter and didn't send it off to another activity yet, we should
202 // close its cursor, which we do by assigning a null cursor to it. Doing this
203 // instead of closing the cursor directly keeps the framework from accessing
204 // the closed cursor later.
205 if (!mAdapterSent && mAdapter != null) {
206 mAdapter.changeCursor(null);
208 // Because we pass the adapter to the next activity, we need to make
209 // sure it doesn't keep a reference to this activity. We can do this
210 // by clearing its DatasetObservers, which setListAdapter(null) does.
211 setListAdapter(null);
217 * This listener gets called when the media scanner starts up, and when the
218 * sd card is unmounted.
220 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
222 public void onReceive(Context context, Intent intent) {
223 MusicUtils.setSpinnerState(QueryBrowserActivity.this);
224 mReScanHandler.sendEmptyMessage(0);
228 private Handler mReScanHandler = new Handler() {
230 public void handleMessage(Message msg) {
231 if (mAdapter != null) {
232 getQueryCursor(mAdapter.getQueryHandler(), null);
234 // if the query results in a null cursor, onQueryComplete() will
235 // call init(), which will post a delayed message to this handler
236 // in order to try again.
241 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
242 switch (requestCode) {
244 if (resultCode == RESULT_CANCELED) {
247 getQueryCursor(mAdapter.getQueryHandler(), null);
253 public void init(Cursor c) {
255 if (mAdapter == null) {
258 mAdapter.changeCursor(c);
260 if (mQueryCursor == null) {
261 MusicUtils.displayDatabaseError(this);
262 setListAdapter(null);
263 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
266 MusicUtils.hideDatabaseError(this);
270 protected void onListItemClick(ListView l, View v, int position, long id)
272 // Dialog doesn't allow us to wait for a result, so we need to store
273 // the info we need for when the dialog posts its result
274 mQueryCursor.moveToPosition(position);
275 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
278 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
279 MediaStore.Audio.Media.MIME_TYPE));
281 if ("artist".equals(selectedType)) {
282 Intent intent = new Intent(Intent.ACTION_PICK);
283 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
284 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
285 intent.putExtra("artist", Long.valueOf(id).toString());
286 startActivity(intent);
287 } else if ("album".equals(selectedType)) {
288 Intent intent = new Intent(Intent.ACTION_PICK);
289 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
290 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
291 intent.putExtra("album", Long.valueOf(id).toString());
292 startActivity(intent);
293 } else if (position >= 0 && id >= 0){
294 long [] list = new long[] { id };
295 MusicUtils.playAll(this, list, 0);
297 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
302 public boolean onOptionsItemSelected(MenuItem item) {
303 switch (item.getItemId()) {
304 case USE_AS_RINGTONE: {
305 // Set the system setting to make this the current ringtone
306 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
311 return super.onOptionsItemSelected(item);
314 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
315 if (filter == null) {
318 String[] ccols = new String[] {
319 BaseColumns._ID, // this will be the artist, album or track ID
320 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
321 MediaStore.Audio.Artists.ARTIST,
322 MediaStore.Audio.Albums.ALBUM,
323 MediaStore.Audio.Media.TITLE,
328 Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
333 async.startQuery(0, null, search, ccols, null, null, null);
335 ret = MusicUtils.query(this, search, ccols, null, null, null);
340 static class QueryListAdapter extends SimpleCursorAdapter {
341 private QueryBrowserActivity mActivity = null;
342 private AsyncQueryHandler mQueryHandler;
343 private String mConstraint = null;
344 private boolean mConstraintIsValid = false;
346 class QueryHandler extends AsyncQueryHandler {
347 QueryHandler(ContentResolver res) {
352 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
353 mActivity.init(cursor);
357 QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
358 int layout, Cursor cursor, String[] from, int[] to) {
359 super(context, layout, cursor, from, to);
360 mActivity = currentactivity;
361 mQueryHandler = new QueryHandler(context.getContentResolver());
364 public void setActivity(QueryBrowserActivity newactivity) {
365 mActivity = newactivity;
368 public AsyncQueryHandler getQueryHandler() {
369 return mQueryHandler;
373 public void bindView(View view, Context context, Cursor cursor) {
375 TextView tv1 = (TextView) view.findViewById(R.id.line1);
376 TextView tv2 = (TextView) view.findViewById(R.id.line2);
377 ImageView iv = (ImageView) view.findViewById(R.id.icon);
378 ViewGroup.LayoutParams p = iv.getLayoutParams();
380 // seen this happen, not sure why
381 DatabaseUtils.dumpCursor(cursor);
384 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
385 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
387 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
388 MediaStore.Audio.Media.MIME_TYPE));
390 if (mimetype == null) {
393 if (mimetype.equals("artist")) {
394 iv.setImageResource(R.drawable.ic_mp_artist_list);
395 String name = cursor.getString(cursor.getColumnIndexOrThrow(
396 MediaStore.Audio.Artists.ARTIST));
397 String displayname = name;
398 boolean isunknown = false;
399 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
400 displayname = context.getString(R.string.unknown_artist_name);
403 tv1.setText(displayname);
405 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
406 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
408 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
409 numalbums, numsongs, isunknown);
411 tv2.setText(songs_albums);
413 } else if (mimetype.equals("album")) {
414 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
415 String name = cursor.getString(cursor.getColumnIndexOrThrow(
416 MediaStore.Audio.Albums.ALBUM));
417 String displayname = name;
418 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
419 displayname = context.getString(R.string.unknown_album_name);
421 tv1.setText(displayname);
423 name = cursor.getString(cursor.getColumnIndexOrThrow(
424 MediaStore.Audio.Artists.ARTIST));
426 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
427 displayname = context.getString(R.string.unknown_artist_name);
429 tv2.setText(displayname);
431 } else if(mimetype.startsWith("audio/") ||
432 mimetype.equals("application/ogg") ||
433 mimetype.equals("application/x-ogg")) {
434 iv.setImageResource(R.drawable.ic_mp_song_list);
435 String name = cursor.getString(cursor.getColumnIndexOrThrow(
436 MediaStore.Audio.Media.TITLE));
439 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
440 MediaStore.Audio.Artists.ARTIST));
441 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
442 displayname = context.getString(R.string.unknown_artist_name);
444 name = cursor.getString(cursor.getColumnIndexOrThrow(
445 MediaStore.Audio.Albums.ALBUM));
446 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
447 name = context.getString(R.string.unknown_album_name);
449 tv2.setText(displayname + " - " + name);
453 public void changeCursor(Cursor cursor) {
454 if (mActivity.isFinishing()) {
457 if (cursor != mActivity.mQueryCursor) {
458 mActivity.mQueryCursor = cursor;
459 super.changeCursor(cursor);
463 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
464 String s = constraint.toString();
465 if (mConstraintIsValid && (
466 (s == null && mConstraint == null) ||
467 (s != null && s.equals(mConstraint)))) {
470 Cursor c = mActivity.getQueryCursor(null, s);
472 mConstraintIsValid = true;
477 private ListView mTrackList;
478 private Cursor mQueryCursor;