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();
96 if (intent.getAction().equals(Intent.ACTION_VIEW)) {
97 // this is something we got from the search bar
98 Uri uri = intent.getData();
99 String path = uri.toString();
100 if (path.startsWith("content://media/external/audio/media/")) {
101 // This is a specific file
102 String id = uri.getLastPathSegment();
103 int [] list = new int[] { Integer.valueOf(id) };
104 MusicUtils.playAll(this, list, 0);
107 } else if (path.startsWith("content://media/external/audio/albums/")) {
108 // This is an album, show the songs on it
109 Intent i = new Intent(Intent.ACTION_PICK);
110 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
111 i.putExtra("album", uri.getLastPathSegment());
115 } else if (path.startsWith("content://media/external/audio/artists/")) {
116 // This is an artist, show the albums for that artist
117 Intent i = new Intent(Intent.ACTION_PICK);
118 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
119 i.putExtra("artist", uri.getLastPathSegment());
125 mFilterString = intent.getStringExtra(SearchManager.QUERY);
127 setContentView(R.layout.query_activity);
128 mTrackList = getListView();
129 mTrackList.setTextFilterEnabled(true);
130 if (mAdapter == null) {
131 mAdapter = new QueryListAdapter(
134 R.layout.track_list_item,
138 setListAdapter(mAdapter);
139 if (TextUtils.isEmpty(mFilterString)) {
140 getQueryCursor(mAdapter.getQueryHandler(), null);
142 mTrackList.setFilterText(mFilterString);
143 mFilterString = null;
146 mAdapter.setActivity(this);
147 setListAdapter(mAdapter);
148 mQueryCursor = mAdapter.getCursor();
149 if (mQueryCursor != null) {
152 getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
157 public void onServiceDisconnected(ComponentName name) {
162 public Object onRetainNonConfigurationInstance() {
168 public void onPause() {
169 mReScanHandler.removeCallbacksAndMessages(null);
174 public void onDestroy() {
175 MusicUtils.unbindFromService(this);
176 unregisterReceiver(mScanListener);
178 if (!mAdapterSent && mAdapter != null) {
179 Cursor c = mAdapter.getCursor();
187 * This listener gets called when the media scanner starts up, and when the
188 * sd card is unmounted.
190 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
192 public void onReceive(Context context, Intent intent) {
193 MusicUtils.setSpinnerState(QueryBrowserActivity.this);
194 mReScanHandler.sendEmptyMessage(0);
198 private Handler mReScanHandler = new Handler() {
200 public void handleMessage(Message msg) {
201 getQueryCursor(mAdapter.getQueryHandler(), null);
202 // if the query results in a null cursor, onQueryComplete() will
203 // call init(), which will post a delayed message to this handler
204 // in order to try again.
209 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
210 switch (requestCode) {
212 if (resultCode == RESULT_CANCELED) {
215 getQueryCursor(mAdapter.getQueryHandler(), null);
221 public void init(Cursor c) {
223 mAdapter.changeCursor(c);
225 if (mQueryCursor == null) {
226 MusicUtils.displayDatabaseError(this);
227 setListAdapter(null);
228 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
231 MusicUtils.hideDatabaseError(this);
235 protected void onListItemClick(ListView l, View v, int position, long id)
237 // Dialog doesn't allow us to wait for a result, so we need to store
238 // the info we need for when the dialog posts its result
239 mQueryCursor.moveToPosition(position);
240 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
243 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
244 MediaStore.Audio.Media.MIME_TYPE));
246 if ("artist".equals(selectedType)) {
247 Intent intent = new Intent(Intent.ACTION_PICK);
248 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
249 intent.putExtra("artist", Long.valueOf(id).toString());
250 startActivity(intent);
251 } else if ("album".equals(selectedType)) {
252 Intent intent = new Intent(Intent.ACTION_PICK);
253 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
254 intent.putExtra("album", Long.valueOf(id).toString());
255 startActivity(intent);
256 } else if (position >= 0 && id >= 0){
257 int [] list = new int[] { (int) id };
258 MusicUtils.playAll(this, list, 0);
260 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
265 public boolean onOptionsItemSelected(MenuItem item) {
266 switch (item.getItemId()) {
267 case USE_AS_RINGTONE: {
268 // Set the system setting to make this the current ringtone
269 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
274 return super.onOptionsItemSelected(item);
277 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
278 if (filter == null) {
281 String[] ccols = new String[] {
282 BaseColumns._ID, // this will be the artist, album or track ID
283 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
284 MediaStore.Audio.Artists.ARTIST,
285 MediaStore.Audio.Albums.ALBUM,
286 MediaStore.Audio.Media.TITLE,
291 Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
296 async.startQuery(0, null, search, ccols, null, null, null);
298 ret = MusicUtils.query(this, search, ccols, null, null, null);
303 static class QueryListAdapter extends SimpleCursorAdapter {
304 private QueryBrowserActivity mActivity = null;
305 private AsyncQueryHandler mQueryHandler;
306 private String mConstraint = null;
307 private boolean mConstraintIsValid = false;
309 class QueryHandler extends AsyncQueryHandler {
310 QueryHandler(ContentResolver res) {
315 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
316 mActivity.init(cursor);
320 QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
321 int layout, Cursor cursor, String[] from, int[] to) {
322 super(context, layout, cursor, from, to);
323 mActivity = currentactivity;
324 mQueryHandler = new QueryHandler(context.getContentResolver());
327 public void setActivity(QueryBrowserActivity newactivity) {
328 mActivity = newactivity;
331 public AsyncQueryHandler getQueryHandler() {
332 return mQueryHandler;
336 public void bindView(View view, Context context, Cursor cursor) {
338 TextView tv1 = (TextView) view.findViewById(R.id.line1);
339 TextView tv2 = (TextView) view.findViewById(R.id.line2);
340 ImageView iv = (ImageView) view.findViewById(R.id.icon);
341 ViewGroup.LayoutParams p = iv.getLayoutParams();
343 // seen this happen, not sure why
344 DatabaseUtils.dumpCursor(cursor);
347 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
348 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
350 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
351 MediaStore.Audio.Media.MIME_TYPE));
353 if (mimetype == null) {
356 if (mimetype.equals("artist")) {
357 iv.setImageResource(R.drawable.ic_mp_artist_list);
358 String name = cursor.getString(cursor.getColumnIndexOrThrow(
359 MediaStore.Audio.Artists.ARTIST));
360 String displayname = name;
361 boolean isunknown = false;
362 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
363 displayname = context.getString(R.string.unknown_artist_name);
366 tv1.setText(displayname);
368 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
369 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
371 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
372 numalbums, numsongs, isunknown);
374 tv2.setText(songs_albums);
376 } else if (mimetype.equals("album")) {
377 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
378 String name = cursor.getString(cursor.getColumnIndexOrThrow(
379 MediaStore.Audio.Albums.ALBUM));
380 String displayname = name;
381 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
382 displayname = context.getString(R.string.unknown_album_name);
384 tv1.setText(displayname);
386 name = cursor.getString(cursor.getColumnIndexOrThrow(
387 MediaStore.Audio.Artists.ARTIST));
389 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
390 displayname = context.getString(R.string.unknown_artist_name);
392 tv2.setText(displayname);
394 } else if(mimetype.startsWith("audio/") ||
395 mimetype.equals("application/ogg") ||
396 mimetype.equals("application/x-ogg")) {
397 iv.setImageResource(R.drawable.ic_mp_song_list);
398 String name = cursor.getString(cursor.getColumnIndexOrThrow(
399 MediaStore.Audio.Media.TITLE));
402 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
403 MediaStore.Audio.Artists.ARTIST));
404 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
405 displayname = context.getString(R.string.unknown_artist_name);
407 name = cursor.getString(cursor.getColumnIndexOrThrow(
408 MediaStore.Audio.Albums.ALBUM));
409 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
410 name = context.getString(R.string.unknown_album_name);
412 tv2.setText(displayname + " - " + name);
416 public void changeCursor(Cursor cursor) {
417 if (cursor != mActivity.mQueryCursor) {
418 mActivity.mQueryCursor = cursor;
419 super.changeCursor(cursor);
423 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
424 String s = constraint.toString();
425 if (mConstraintIsValid && (
426 (s == null && mConstraint == null) ||
427 (s != null && s.equals(mConstraint)))) {
430 Cursor c = mActivity.getQueryCursor(null, s);
432 mConstraintIsValid = true;
437 private ListView mTrackList;
438 private Cursor mQueryCursor;