QueryBrowserActivity.java revision 93a5b4355c9d844adc329a336668d4cb2ff91754
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 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 long [] list = new long[] { Long.valueOf(id) }; 104 MusicUtils.playAll(this, list, 0); 105 finish(); 106 return; 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()); 112 startActivity(i); 113 finish(); 114 return; 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()); 120 startActivity(i); 121 finish(); 122 return; 123 } 124 } 125 mFilterString = intent.getStringExtra(SearchManager.QUERY); 126 127 setContentView(R.layout.query_activity); 128 mTrackList = getListView(); 129 mTrackList.setTextFilterEnabled(true); 130 if (mAdapter == null) { 131 mAdapter = new QueryListAdapter( 132 getApplication(), 133 this, 134 R.layout.track_list_item, 135 null, // cursor 136 new String[] {}, 137 new int[] {}); 138 setListAdapter(mAdapter); 139 if (TextUtils.isEmpty(mFilterString)) { 140 getQueryCursor(mAdapter.getQueryHandler(), null); 141 } else { 142 mTrackList.setFilterText(mFilterString); 143 mFilterString = null; 144 } 145 } else { 146 mAdapter.setActivity(this); 147 setListAdapter(mAdapter); 148 mQueryCursor = mAdapter.getCursor(); 149 if (mQueryCursor != null) { 150 init(mQueryCursor); 151 } else { 152 getQueryCursor(mAdapter.getQueryHandler(), mFilterString); 153 } 154 } 155 } 156 157 public void onServiceDisconnected(ComponentName name) { 158 159 } 160 161 @Override 162 public Object onRetainNonConfigurationInstance() { 163 mAdapterSent = true; 164 return mAdapter; 165 } 166 167 @Override 168 public void onPause() { 169 mReScanHandler.removeCallbacksAndMessages(null); 170 super.onPause(); 171 } 172 173 @Override 174 public void onDestroy() { 175 MusicUtils.unbindFromService(this); 176 unregisterReceiver(mScanListener); 177 super.onDestroy(); 178 if (!mAdapterSent && mAdapter != null) { 179 Cursor c = mAdapter.getCursor(); 180 if (c != null) { 181 c.close(); 182 } 183 } 184 // Because we pass the adapter to the next activity, we need to make 185 // sure it doesn't keep a reference to this activity. We can do this 186 // by clearing its DatasetObservers, which setListAdapter(null) does. 187 setListAdapter(null); 188 mAdapter = null; 189 } 190 191 /* 192 * This listener gets called when the media scanner starts up, and when the 193 * sd card is unmounted. 194 */ 195 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 196 @Override 197 public void onReceive(Context context, Intent intent) { 198 MusicUtils.setSpinnerState(QueryBrowserActivity.this); 199 mReScanHandler.sendEmptyMessage(0); 200 } 201 }; 202 203 private Handler mReScanHandler = new Handler() { 204 @Override 205 public void handleMessage(Message msg) { 206 getQueryCursor(mAdapter.getQueryHandler(), null); 207 // if the query results in a null cursor, onQueryComplete() will 208 // call init(), which will post a delayed message to this handler 209 // in order to try again. 210 } 211 }; 212 213 @Override 214 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 215 switch (requestCode) { 216 case SCAN_DONE: 217 if (resultCode == RESULT_CANCELED) { 218 finish(); 219 } else { 220 getQueryCursor(mAdapter.getQueryHandler(), null); 221 } 222 break; 223 } 224 } 225 226 public void init(Cursor c) { 227 228 mAdapter.changeCursor(c); 229 230 if (mQueryCursor == null) { 231 MusicUtils.displayDatabaseError(this); 232 setListAdapter(null); 233 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 234 return; 235 } 236 MusicUtils.hideDatabaseError(this); 237 } 238 239 @Override 240 protected void onListItemClick(ListView l, View v, int position, long id) 241 { 242 // Dialog doesn't allow us to wait for a result, so we need to store 243 // the info we need for when the dialog posts its result 244 mQueryCursor.moveToPosition(position); 245 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) { 246 return; 247 } 248 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow( 249 MediaStore.Audio.Media.MIME_TYPE)); 250 251 if ("artist".equals(selectedType)) { 252 Intent intent = new Intent(Intent.ACTION_PICK); 253 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); 254 intent.putExtra("artist", Long.valueOf(id).toString()); 255 startActivity(intent); 256 } else if ("album".equals(selectedType)) { 257 Intent intent = new Intent(Intent.ACTION_PICK); 258 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 259 intent.putExtra("album", Long.valueOf(id).toString()); 260 startActivity(intent); 261 } else if (position >= 0 && id >= 0){ 262 long [] list = new long[] { id }; 263 MusicUtils.playAll(this, list, 0); 264 } else { 265 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id); 266 } 267 } 268 269 @Override 270 public boolean onOptionsItemSelected(MenuItem item) { 271 switch (item.getItemId()) { 272 case USE_AS_RINGTONE: { 273 // Set the system setting to make this the current ringtone 274 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId()); 275 return true; 276 } 277 278 } 279 return super.onOptionsItemSelected(item); 280 } 281 282 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) { 283 if (filter == null) { 284 filter = ""; 285 } 286 String[] ccols = new String[] { 287 BaseColumns._ID, // this will be the artist, album or track ID 288 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album" 289 MediaStore.Audio.Artists.ARTIST, 290 MediaStore.Audio.Albums.ALBUM, 291 MediaStore.Audio.Media.TITLE, 292 "data1", 293 "data2" 294 }; 295 296 Uri search = Uri.parse("content://media/external/audio/search/fancy/" + 297 Uri.encode(filter)); 298 299 Cursor ret = null; 300 if (async != null) { 301 async.startQuery(0, null, search, ccols, null, null, null); 302 } else { 303 ret = MusicUtils.query(this, search, ccols, null, null, null); 304 } 305 return ret; 306 } 307 308 static class QueryListAdapter extends SimpleCursorAdapter { 309 private QueryBrowserActivity mActivity = null; 310 private AsyncQueryHandler mQueryHandler; 311 private String mConstraint = null; 312 private boolean mConstraintIsValid = false; 313 314 class QueryHandler extends AsyncQueryHandler { 315 QueryHandler(ContentResolver res) { 316 super(res); 317 } 318 319 @Override 320 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 321 mActivity.init(cursor); 322 } 323 } 324 325 QueryListAdapter(Context context, QueryBrowserActivity currentactivity, 326 int layout, Cursor cursor, String[] from, int[] to) { 327 super(context, layout, cursor, from, to); 328 mActivity = currentactivity; 329 mQueryHandler = new QueryHandler(context.getContentResolver()); 330 } 331 332 public void setActivity(QueryBrowserActivity newactivity) { 333 mActivity = newactivity; 334 } 335 336 public AsyncQueryHandler getQueryHandler() { 337 return mQueryHandler; 338 } 339 340 @Override 341 public void bindView(View view, Context context, Cursor cursor) { 342 343 TextView tv1 = (TextView) view.findViewById(R.id.line1); 344 TextView tv2 = (TextView) view.findViewById(R.id.line2); 345 ImageView iv = (ImageView) view.findViewById(R.id.icon); 346 ViewGroup.LayoutParams p = iv.getLayoutParams(); 347 if (p == null) { 348 // seen this happen, not sure why 349 DatabaseUtils.dumpCursor(cursor); 350 return; 351 } 352 p.width = ViewGroup.LayoutParams.WRAP_CONTENT; 353 p.height = ViewGroup.LayoutParams.WRAP_CONTENT; 354 355 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow( 356 MediaStore.Audio.Media.MIME_TYPE)); 357 358 if (mimetype == null) { 359 mimetype = "audio/"; 360 } 361 if (mimetype.equals("artist")) { 362 iv.setImageResource(R.drawable.ic_mp_artist_list); 363 String name = cursor.getString(cursor.getColumnIndexOrThrow( 364 MediaStore.Audio.Artists.ARTIST)); 365 String displayname = name; 366 boolean isunknown = false; 367 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 368 displayname = context.getString(R.string.unknown_artist_name); 369 isunknown = true; 370 } 371 tv1.setText(displayname); 372 373 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1")); 374 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2")); 375 376 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context, 377 numalbums, numsongs, isunknown); 378 379 tv2.setText(songs_albums); 380 381 } else if (mimetype.equals("album")) { 382 iv.setImageResource(R.drawable.albumart_mp_unknown_list); 383 String name = cursor.getString(cursor.getColumnIndexOrThrow( 384 MediaStore.Audio.Albums.ALBUM)); 385 String displayname = name; 386 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 387 displayname = context.getString(R.string.unknown_album_name); 388 } 389 tv1.setText(displayname); 390 391 name = cursor.getString(cursor.getColumnIndexOrThrow( 392 MediaStore.Audio.Artists.ARTIST)); 393 displayname = name; 394 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 395 displayname = context.getString(R.string.unknown_artist_name); 396 } 397 tv2.setText(displayname); 398 399 } else if(mimetype.startsWith("audio/") || 400 mimetype.equals("application/ogg") || 401 mimetype.equals("application/x-ogg")) { 402 iv.setImageResource(R.drawable.ic_mp_song_list); 403 String name = cursor.getString(cursor.getColumnIndexOrThrow( 404 MediaStore.Audio.Media.TITLE)); 405 tv1.setText(name); 406 407 String displayname = cursor.getString(cursor.getColumnIndexOrThrow( 408 MediaStore.Audio.Artists.ARTIST)); 409 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) { 410 displayname = context.getString(R.string.unknown_artist_name); 411 } 412 name = cursor.getString(cursor.getColumnIndexOrThrow( 413 MediaStore.Audio.Albums.ALBUM)); 414 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) { 415 name = context.getString(R.string.unknown_album_name); 416 } 417 tv2.setText(displayname + " - " + name); 418 } 419 } 420 @Override 421 public void changeCursor(Cursor cursor) { 422 if (cursor != mActivity.mQueryCursor) { 423 mActivity.mQueryCursor = cursor; 424 super.changeCursor(cursor); 425 } 426 } 427 @Override 428 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 429 String s = constraint.toString(); 430 if (mConstraintIsValid && ( 431 (s == null && mConstraint == null) || 432 (s != null && s.equals(mConstraint)))) { 433 return getCursor(); 434 } 435 Cursor c = mActivity.getQueryCursor(null, s); 436 mConstraint = s; 437 mConstraintIsValid = true; 438 return c; 439 } 440 } 441 442 private ListView mTrackList; 443 private Cursor mQueryCursor; 444} 445 446