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.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
28 import android.database.Cursor;
29 import android.database.DatabaseUtils;
30 import android.media.AudioManager;
31 import android.media.MediaFile;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.provider.BaseColumns;
37 import android.provider.MediaStore;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.KeyEvent;
41 import android.view.MenuItem;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.Window;
45 import android.view.ViewGroup.OnHierarchyChangeListener;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.SimpleCursorAdapter;
49 import android.widget.TextView;
51 import java.util.ArrayList;
53 public class QueryBrowserActivity extends ListActivity implements MusicUtils.Defs
55 private final static int PLAY_NOW = 0;
56 private final static int ADD_TO_QUEUE = 1;
57 private final static int PLAY_NEXT = 2;
58 private final static int PLAY_ARTIST = 3;
59 private final static int EXPLORE_ARTIST = 4;
60 private final static int PLAY_ALBUM = 5;
61 private final static int EXPLORE_ALBUM = 6;
62 private final static int REQUERY = 3;
63 private QueryListAdapter mAdapter;
64 private boolean mAdapterSent;
65 private String mFilterString = "";
67 public QueryBrowserActivity()
71 /** Called when the activity is first created. */
73 public void onCreate(Bundle icicle)
75 super.onCreate(icicle);
76 setVolumeControlStream(AudioManager.STREAM_MUSIC);
77 MusicUtils.bindToService(this);
78 IntentFilter f = new IntentFilter();
79 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
80 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
81 f.addDataScheme("file");
82 registerReceiver(mScanListener, f);
85 Intent intent = getIntent();
87 if (intent.getAction().equals(Intent.ACTION_VIEW)) {
88 // this is something we got from the search bar
89 Uri uri = intent.getData();
90 String path = uri.toString();
91 if (path.startsWith("content://media/external/audio/media/")) {
92 // This is a specific file
93 String id = uri.getLastPathSegment();
94 int [] list = new int[] { Integer.valueOf(id) };
95 MusicUtils.playAll(this, list, 0);
98 } else if (path.startsWith("content://media/external/audio/albums/")) {
99 // This is an album, show the songs on it
100 Intent i = new Intent(Intent.ACTION_PICK);
101 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
102 i.putExtra("album", uri.getLastPathSegment());
106 } else if (path.startsWith("content://media/external/audio/artists/")) {
107 // This is an artist, show the albums for that artist
108 Intent i = new Intent(Intent.ACTION_PICK);
109 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
110 i.putExtra("artist", uri.getLastPathSegment());
116 mFilterString = intent.getStringExtra(SearchManager.QUERY);
119 setContentView(R.layout.query_activity);
120 mTrackList = getListView();
121 mTrackList.setTextFilterEnabled(true);
122 mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
123 if (mAdapter == null) {
124 mAdapter = new QueryListAdapter(
127 R.layout.track_list_item,
131 setListAdapter(mAdapter);
132 if (TextUtils.isEmpty(mFilterString)) {
133 getQueryCursor(mAdapter.getQueryHandler(), null);
135 mTrackList.setFilterText(mFilterString);
136 mFilterString = null;
139 mAdapter.setActivity(this);
140 setListAdapter(mAdapter);
141 mQueryCursor = mAdapter.getCursor();
142 if (mQueryCursor != null) {
145 getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
151 public Object onRetainNonConfigurationInstance() {
157 public void onPause() {
158 mReScanHandler.removeCallbacksAndMessages(null);
163 public void onDestroy() {
164 MusicUtils.unbindFromService(this);
165 unregisterReceiver(mScanListener);
167 if (!mAdapterSent && mAdapter != null) {
168 Cursor c = mAdapter.getCursor();
176 * This listener gets called when the media scanner starts up, and when the
177 * sd card is unmounted.
179 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
181 public void onReceive(Context context, Intent intent) {
182 MusicUtils.setSpinnerState(QueryBrowserActivity.this);
183 mReScanHandler.sendEmptyMessage(0);
187 private Handler mReScanHandler = new Handler() {
189 public void handleMessage(Message msg) {
190 getQueryCursor(mAdapter.getQueryHandler(), null);
191 // if the query results in a null cursor, onQueryComplete() will
192 // call init(), which will post a delayed message to this handler
193 // in order to try again.
198 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
199 switch (requestCode) {
201 if (resultCode == RESULT_CANCELED) {
204 getQueryCursor(mAdapter.getQueryHandler(), null);
210 public void init(Cursor c) {
212 mAdapter.changeCursor(c);
214 if (mQueryCursor == null) {
215 MusicUtils.displayDatabaseError(this);
216 setListAdapter(null);
217 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
220 MusicUtils.hideDatabaseError(this);
224 protected void onListItemClick(ListView l, View v, int position, long id)
226 // Dialog doesn't allow us to wait for a result, so we need to store
227 // the info we need for when the dialog posts its result
228 mQueryCursor.moveToPosition(position);
229 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
232 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
233 MediaStore.Audio.Media.MIME_TYPE));
235 if ("artist".equals(selectedType)) {
236 Intent intent = new Intent(Intent.ACTION_PICK);
237 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
238 intent.putExtra("artist", Long.valueOf(id).toString());
239 startActivity(intent);
240 } else if ("album".equals(selectedType)) {
241 Intent intent = new Intent(Intent.ACTION_PICK);
242 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
243 intent.putExtra("album", Long.valueOf(id).toString());
244 startActivity(intent);
245 } else if (position >= 0 && id >= 0){
246 int [] list = new int[] { (int) id };
247 MusicUtils.playAll(this, list, 0);
249 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
254 public boolean onOptionsItemSelected(MenuItem item) {
255 switch (item.getItemId()) {
256 case USE_AS_RINGTONE: {
257 // Set the system setting to make this the current ringtone
258 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
263 return super.onOptionsItemSelected(item);
266 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
267 if (filter == null) {
270 String[] ccols = new String[] {
271 BaseColumns._ID, // this will be the artist, album or track ID
272 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
273 MediaStore.Audio.Artists.ARTIST,
274 MediaStore.Audio.Albums.ALBUM,
275 MediaStore.Audio.Media.TITLE,
280 Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
285 async.startQuery(0, null, search, ccols, null, null, null);
287 ret = MusicUtils.query(this, search, ccols, null, null, null);
292 static class QueryListAdapter extends SimpleCursorAdapter {
293 private QueryBrowserActivity mActivity = null;
294 private AsyncQueryHandler mQueryHandler;
295 private String mConstraint = null;
296 private boolean mConstraintIsValid = false;
298 class QueryHandler extends AsyncQueryHandler {
299 QueryHandler(ContentResolver res) {
304 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
305 mActivity.init(cursor);
309 QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
310 int layout, Cursor cursor, String[] from, int[] to) {
311 super(context, layout, cursor, from, to);
312 mActivity = currentactivity;
313 mQueryHandler = new QueryHandler(context.getContentResolver());
316 public void setActivity(QueryBrowserActivity newactivity) {
317 mActivity = newactivity;
320 public AsyncQueryHandler getQueryHandler() {
321 return mQueryHandler;
325 public void bindView(View view, Context context, Cursor cursor) {
327 TextView tv1 = (TextView) view.findViewById(R.id.line1);
328 TextView tv2 = (TextView) view.findViewById(R.id.line2);
329 ImageView iv = (ImageView) view.findViewById(R.id.icon);
330 ViewGroup.LayoutParams p = iv.getLayoutParams();
332 // seen this happen, not sure why
333 DatabaseUtils.dumpCursor(cursor);
336 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
337 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
339 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
340 MediaStore.Audio.Media.MIME_TYPE));
342 if (mimetype == null) {
345 if (mimetype.equals("artist")) {
346 iv.setImageResource(R.drawable.ic_mp_artist_list);
347 String name = cursor.getString(cursor.getColumnIndexOrThrow(
348 MediaStore.Audio.Artists.ARTIST));
349 String displayname = name;
350 boolean isunknown = false;
351 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
352 displayname = context.getString(R.string.unknown_artist_name);
355 tv1.setText(displayname);
357 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
358 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
360 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
361 numalbums, numsongs, isunknown);
363 tv2.setText(songs_albums);
365 } else if (mimetype.equals("album")) {
366 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
367 String name = cursor.getString(cursor.getColumnIndexOrThrow(
368 MediaStore.Audio.Albums.ALBUM));
369 String displayname = name;
370 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
371 displayname = context.getString(R.string.unknown_album_name);
373 tv1.setText(displayname);
375 name = cursor.getString(cursor.getColumnIndexOrThrow(
376 MediaStore.Audio.Artists.ARTIST));
378 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
379 displayname = context.getString(R.string.unknown_artist_name);
381 tv2.setText(displayname);
383 } else if(mimetype.startsWith("audio/") ||
384 mimetype.equals("application/ogg") ||
385 mimetype.equals("application/x-ogg")) {
386 iv.setImageResource(R.drawable.ic_mp_song_list);
387 String name = cursor.getString(cursor.getColumnIndexOrThrow(
388 MediaStore.Audio.Media.TITLE));
391 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
392 MediaStore.Audio.Artists.ARTIST));
393 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
394 displayname = context.getString(R.string.unknown_artist_name);
396 name = cursor.getString(cursor.getColumnIndexOrThrow(
397 MediaStore.Audio.Albums.ALBUM));
398 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
399 name = context.getString(R.string.unknown_album_name);
401 tv2.setText(displayname + " - " + name);
405 public void changeCursor(Cursor cursor) {
406 if (cursor != mActivity.mQueryCursor) {
407 mActivity.mQueryCursor = cursor;
408 super.changeCursor(cursor);
412 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
413 String s = constraint.toString();
414 if (mConstraintIsValid && (
415 (s == null && mConstraint == null) ||
416 (s != null && s.equals(mConstraint)))) {
419 Cursor c = mActivity.getQueryCursor(null, s);
421 mConstraintIsValid = true;
426 private ListView mTrackList;
427 private Cursor mQueryCursor;