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);
202 if (!mAdapterSent && mAdapter != null) {
203 Cursor c = mAdapter.getCursor();
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);
216 * This listener gets called when the media scanner starts up, and when the
217 * sd card is unmounted.
219 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
221 public void onReceive(Context context, Intent intent) {
222 MusicUtils.setSpinnerState(QueryBrowserActivity.this);
223 mReScanHandler.sendEmptyMessage(0);
227 private Handler mReScanHandler = new Handler() {
229 public void handleMessage(Message msg) {
230 if (mAdapter != null) {
231 getQueryCursor(mAdapter.getQueryHandler(), null);
233 // if the query results in a null cursor, onQueryComplete() will
234 // call init(), which will post a delayed message to this handler
235 // in order to try again.
240 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
241 switch (requestCode) {
243 if (resultCode == RESULT_CANCELED) {
246 getQueryCursor(mAdapter.getQueryHandler(), null);
252 public void init(Cursor c) {
254 if (mAdapter == null) {
257 mAdapter.changeCursor(c);
259 if (mQueryCursor == null) {
260 MusicUtils.displayDatabaseError(this);
261 setListAdapter(null);
262 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
265 MusicUtils.hideDatabaseError(this);
269 protected void onListItemClick(ListView l, View v, int position, long id)
271 // Dialog doesn't allow us to wait for a result, so we need to store
272 // the info we need for when the dialog posts its result
273 mQueryCursor.moveToPosition(position);
274 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
277 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
278 MediaStore.Audio.Media.MIME_TYPE));
280 if ("artist".equals(selectedType)) {
281 Intent intent = new Intent(Intent.ACTION_PICK);
282 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
283 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
284 intent.putExtra("artist", Long.valueOf(id).toString());
285 startActivity(intent);
286 } else if ("album".equals(selectedType)) {
287 Intent intent = new Intent(Intent.ACTION_PICK);
288 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
289 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
290 intent.putExtra("album", Long.valueOf(id).toString());
291 startActivity(intent);
292 } else if (position >= 0 && id >= 0){
293 long [] list = new long[] { id };
294 MusicUtils.playAll(this, list, 0);
296 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
301 public boolean onOptionsItemSelected(MenuItem item) {
302 switch (item.getItemId()) {
303 case USE_AS_RINGTONE: {
304 // Set the system setting to make this the current ringtone
305 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
310 return super.onOptionsItemSelected(item);
313 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
314 if (filter == null) {
317 String[] ccols = new String[] {
318 BaseColumns._ID, // this will be the artist, album or track ID
319 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
320 MediaStore.Audio.Artists.ARTIST,
321 MediaStore.Audio.Albums.ALBUM,
322 MediaStore.Audio.Media.TITLE,
327 Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
332 async.startQuery(0, null, search, ccols, null, null, null);
334 ret = MusicUtils.query(this, search, ccols, null, null, null);
339 static class QueryListAdapter extends SimpleCursorAdapter {
340 private QueryBrowserActivity mActivity = null;
341 private AsyncQueryHandler mQueryHandler;
342 private String mConstraint = null;
343 private boolean mConstraintIsValid = false;
345 class QueryHandler extends AsyncQueryHandler {
346 QueryHandler(ContentResolver res) {
351 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
352 mActivity.init(cursor);
356 QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
357 int layout, Cursor cursor, String[] from, int[] to) {
358 super(context, layout, cursor, from, to);
359 mActivity = currentactivity;
360 mQueryHandler = new QueryHandler(context.getContentResolver());
363 public void setActivity(QueryBrowserActivity newactivity) {
364 mActivity = newactivity;
367 public AsyncQueryHandler getQueryHandler() {
368 return mQueryHandler;
372 public void bindView(View view, Context context, Cursor cursor) {
374 TextView tv1 = (TextView) view.findViewById(R.id.line1);
375 TextView tv2 = (TextView) view.findViewById(R.id.line2);
376 ImageView iv = (ImageView) view.findViewById(R.id.icon);
377 ViewGroup.LayoutParams p = iv.getLayoutParams();
379 // seen this happen, not sure why
380 DatabaseUtils.dumpCursor(cursor);
383 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
384 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
386 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
387 MediaStore.Audio.Media.MIME_TYPE));
389 if (mimetype == null) {
392 if (mimetype.equals("artist")) {
393 iv.setImageResource(R.drawable.ic_mp_artist_list);
394 String name = cursor.getString(cursor.getColumnIndexOrThrow(
395 MediaStore.Audio.Artists.ARTIST));
396 String displayname = name;
397 boolean isunknown = false;
398 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
399 displayname = context.getString(R.string.unknown_artist_name);
402 tv1.setText(displayname);
404 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
405 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
407 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
408 numalbums, numsongs, isunknown);
410 tv2.setText(songs_albums);
412 } else if (mimetype.equals("album")) {
413 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
414 String name = cursor.getString(cursor.getColumnIndexOrThrow(
415 MediaStore.Audio.Albums.ALBUM));
416 String displayname = name;
417 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
418 displayname = context.getString(R.string.unknown_album_name);
420 tv1.setText(displayname);
422 name = cursor.getString(cursor.getColumnIndexOrThrow(
423 MediaStore.Audio.Artists.ARTIST));
425 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
426 displayname = context.getString(R.string.unknown_artist_name);
428 tv2.setText(displayname);
430 } else if(mimetype.startsWith("audio/") ||
431 mimetype.equals("application/ogg") ||
432 mimetype.equals("application/x-ogg")) {
433 iv.setImageResource(R.drawable.ic_mp_song_list);
434 String name = cursor.getString(cursor.getColumnIndexOrThrow(
435 MediaStore.Audio.Media.TITLE));
438 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
439 MediaStore.Audio.Artists.ARTIST));
440 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
441 displayname = context.getString(R.string.unknown_artist_name);
443 name = cursor.getString(cursor.getColumnIndexOrThrow(
444 MediaStore.Audio.Albums.ALBUM));
445 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
446 name = context.getString(R.string.unknown_album_name);
448 tv2.setText(displayname + " - " + name);
452 public void changeCursor(Cursor cursor) {
453 if (cursor != mActivity.mQueryCursor) {
454 mActivity.mQueryCursor = cursor;
455 super.changeCursor(cursor);
459 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
460 String s = constraint.toString();
461 if (mConstraintIsValid && (
462 (s == null && mConstraint == null) ||
463 (s != null && s.equals(mConstraint)))) {
466 Cursor c = mActivity.getQueryCursor(null, s);
468 mConstraintIsValid = true;
473 private ListView mTrackList;
474 private Cursor mQueryCursor;