QueryBrowserActivity.java revision 42bcc218ce4c330cc609326d168e288f3559c64f
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 17package com.android.music; 18 19import android.app.ListActivity; 20import android.app.SearchManager; 21import android.content.AsyncQueryHandler; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.ServiceConnection; 29 30import android.database.Cursor; 31import android.database.DatabaseUtils; 32import android.media.AudioManager; 33import android.media.MediaFile; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Message; 39import android.provider.BaseColumns; 40import android.provider.MediaStore; 41import android.text.TextUtils; 42import android.util.Log; 43import android.view.KeyEvent; 44import android.view.MenuItem; 45import android.view.View; 46import android.view.ViewGroup; 47import android.view.Window; 48import android.view.ViewGroup.OnHierarchyChangeListener; 49import android.widget.ImageView; 50import android.widget.ListView; 51import android.widget.SimpleCursorAdapter; 52import android.widget.TextView; 53 54import java.util.ArrayList; 55 56public class QueryBrowserActivity extends ListActivity 57implements MusicUtils.Defs, ServiceConnection 58{ 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 = ""; 70 71 public QueryBrowserActivity() 72 { 73 } 74 75 /** Called when the activity is first created. */ 76 @Override 77 public void onCreate(Bundle icicle) 78 { 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 84 } 85 86 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); 93 94 Intent intent = getIntent(); 95 String action = intent != null ? intent.getAction() : null; 96 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); 106 finish(); 107 return; 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()); 113 startActivity(i); 114 finish(); 115 return; 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()); 121 startActivity(i); 122 finish(); 123 return; 124 } 125 } 126 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); 133 if (focus != null) { 134 if (focus.startsWith("audio/") && title != null) { 135 mFilterString = title; 136 } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { 137 if (album != null) { 138 mFilterString = album; 139 if (artist != null) { 140 mFilterString = mFilterString + " " + artist; 141 } 142 } 143 } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { 144 if (artist != null) { 145 mFilterString = artist; 146 } 147 } 148 } 149 } 150 151 setContentView(R.layout.query_activity); 152 mTrackList = getListView(); 153 mTrackList.setTextFilterEnabled(true); 154 if (mAdapter == null) { 155 mAdapter = new QueryListAdapter( 156 getApplication(), 157 this, 158 R.layout.track_list_item, 159 null, // cursor 160 new String[] {}, 161 new int[] {}); 162 setListAdapter(mAdapter); 163 if (TextUtils.isEmpty(mFilterString)) { 164 getQueryCursor(mAdapter.getQueryHandler(), null); 165 } else { 166 mTrackList.setFilterText(mFilterString); 167 mFilterString = null; 168 } 169 } else { 170 mAdapter.setActivity(this); 171 setListAdapter(mAdapter); 172 mQueryCursor = mAdapter.getCursor(); 173 if (mQueryCursor != null) { 174 init(mQueryCursor); 175 } else { 176 getQueryCursor(mAdapter.getQueryHandler(), mFilterString); 177 } 178 } 179 } 180 181 public void onServiceDisconnected(ComponentName name) { 182 183 } 184 185 @Override 186 public Object onRetainNonConfigurationInstance() { 187 mAdapterSent = true; 188 return mAdapter; 189 } 190 191 @Override 192 public void onPause() { 193 mReScanHandler.removeCallbacksAndMessages(null); 194 super.onPause(); 195 } 196 197 @Override 198 public void onDestroy() { 199 MusicUtils.unbindFromService(this); 200 unregisterReceiver(mScanListener); 201 super.onDestroy(); 202 if (!mAdapterSent && mAdapter != null) { 203 Cursor c = mAdapter.getCursor(); 204 if (c != null) { 205 c.close(); 206 } 207 } 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); 212 mAdapter = null; 213 } 214 215 /* 216 * This listener gets called when the media scanner starts up, and when the 217 * sd card is unmounted. 218 */ 219 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 220 @Override 221 public void onReceive(Context context, Intent intent) { 222 MusicUtils.setSpinnerState(QueryBrowserActivity.this); 223 mReScanHandler.sendEmptyMessage(0); 224 } 225 }; 226 227 private Handler mReScanHandler = new Handler() { 228 @Override 229 public void handleMessage(Message msg) { 230 if (mAdapter != null) { 231 getQueryCursor(mAdapter.getQueryHandler(), null); 232 } 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. 236 } 237 }; 238 239 @Override 240 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 241 switch (requestCode) { 242 case SCAN_DONE: 243 if (resultCode == RESULT_CANCELED) { 244 finish(); 245 } else { 246 getQueryCursor(mAdapter.getQueryHandler(), null); 247 } 248 break; 249 } 250 } 251 252 public void init(Cursor c) { 253 254 if (mAdapter == null) { 255 return; 256 } 257 mAdapter.changeCursor(c); 258 259 if (mQueryCursor == null) { 260 MusicUtils.displayDatabaseError(this); 261 setListAdapter(null); 262 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 263 return; 264 } 265 MusicUtils.hideDatabaseError(this); 266 } 267 268 @Override 269 protected void onListItemClick(ListView l, View v, int position, long id) 270 { 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()) { 275 return; 276 } 277 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow( 278 MediaStore.Audio.Media.MIME_TYPE)); 279 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); 295 } else { 296 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id); 297 } 298 } 299 300 @Override 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()); 306 return true; 307 } 308 309 } 310 return super.onOptionsItemSelected(item); 311 } 312 313 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) { 314 if (filter == null) { 315 filter = ""; 316 } 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, 323 "data1", 324 "data2" 325 }; 326 327 Uri search = Uri.parse("content://media/external/audio/search/fancy/" + 328 Uri.encode(filter)); 329 330 Cursor ret = null; 331 if (async != null) { 332 async.startQuery(0, null, search, ccols, null, null, null); 333 } else { 334 ret = MusicUtils.query(this, search, ccols, null, null, null); 335 } 336 return ret; 337 } 338 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; 344 345 class QueryHandler extends AsyncQueryHandler { 346 QueryHandler(ContentResolver res) { 347 super(res); 348 } 349 350 @Override 351 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 352 mActivity.init(cursor); 353 } 354 } 355 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()); 361 } 362 363 public void setActivity(QueryBrowserActivity newactivity) { 364 mActivity = newactivity; 365 } 366 367 public AsyncQueryHandler getQueryHandler() { 368 return mQueryHandler; 369 } 370 371 @Override 372 public void bindView(View view, Context context, Cursor cursor) { 373 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(); 378 if (p == null) { 379 // seen this happen, not sure why 380 DatabaseUtils.dumpCursor(cursor); 381 return; 382 } 383 p.width = ViewGroup.LayoutParams.WRAP_CONTENT; 384 p.height = ViewGroup.LayoutParams.WRAP_CONTENT; 385 386 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow( 387 MediaStore.Audio.Media.MIME_TYPE)); 388 389 if (mimetype == null) { 390 mimetype = "audio/"; 391 } 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); 400 isunknown = true; 401 } 402 tv1.setText(displayname); 403 404 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1")); 405 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2")); 406 407 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context, 408 numalbums, numsongs, isunknown); 409 410 tv2.setText(songs_albums); 411 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); 419 } 420 tv1.setText(displayname); 421 422 name = cursor.getString(cursor.getColumnIndexOrThrow( 423 MediaStore.Audio.Artists.ARTIST)); 424 displayname = name; 425 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 426 displayname = context.getString(R.string.unknown_artist_name); 427 } 428 tv2.setText(displayname); 429 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)); 436 tv1.setText(name); 437 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); 442 } 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); 447 } 448 tv2.setText(displayname + " - " + name); 449 } 450 } 451 @Override 452 public void changeCursor(Cursor cursor) { 453 if (cursor != mActivity.mQueryCursor) { 454 mActivity.mQueryCursor = cursor; 455 super.changeCursor(cursor); 456 } 457 } 458 @Override 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)))) { 464 return getCursor(); 465 } 466 Cursor c = mActivity.getQueryCursor(null, s); 467 mConstraint = s; 468 mConstraintIsValid = true; 469 return c; 470 } 471 } 472 473 private ListView mTrackList; 474 private Cursor mQueryCursor; 475} 476 477