QueryBrowserActivity.java revision d72c7f5d49d1c1c1afa281459d18318390400611
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.ContentResolver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27 28import android.database.Cursor; 29import android.database.DatabaseUtils; 30import android.media.AudioManager; 31import android.media.MediaFile; 32import android.net.Uri; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.Message; 36import android.provider.MediaStore; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.KeyEvent; 40import android.view.MenuItem; 41import android.view.View; 42import android.view.ViewGroup; 43import android.view.Window; 44import android.view.ViewGroup.OnHierarchyChangeListener; 45import android.widget.ImageView; 46import android.widget.ListView; 47import android.widget.SimpleCursorAdapter; 48import android.widget.TextView; 49 50import java.util.ArrayList; 51 52public class QueryBrowserActivity extends ListActivity implements MusicUtils.Defs 53{ 54 private final static int PLAY_NOW = 0; 55 private final static int ADD_TO_QUEUE = 1; 56 private final static int PLAY_NEXT = 2; 57 private final static int PLAY_ARTIST = 3; 58 private final static int EXPLORE_ARTIST = 4; 59 private final static int PLAY_ALBUM = 5; 60 private final static int EXPLORE_ALBUM = 6; 61 private final static int REQUERY = 3; 62 private QueryListAdapter mAdapter; 63 private boolean mAdapterSent; 64 private String mFilterString = ""; 65 66 public QueryBrowserActivity() 67 { 68 } 69 70 /** Called when the activity is first created. */ 71 @Override 72 public void onCreate(Bundle icicle) 73 { 74 super.onCreate(icicle); 75 setVolumeControlStream(AudioManager.STREAM_MUSIC); 76 MusicUtils.bindToService(this); 77 IntentFilter f = new IntentFilter(); 78 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 79 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 80 f.addDataScheme("file"); 81 registerReceiver(mScanListener, f); 82 83 if (icicle == null) { 84 Intent intent = getIntent(); 85 86 if (intent.getAction().equals(Intent.ACTION_VIEW)) { 87 // this is something we got from the search bar 88 Uri uri = intent.getData(); 89 String path = uri.toString(); 90 if (path.startsWith("content://media/external/audio/media/")) { 91 // This is a specific file 92 String id = uri.getLastPathSegment(); 93 int [] list = new int[] { Integer.valueOf(id) }; 94 MusicUtils.playAll(this, list, 0); 95 finish(); 96 return; 97 } else if (path.startsWith("content://media/external/audio/albums/")) { 98 // This is an album, show the songs on it 99 Intent i = new Intent(Intent.ACTION_PICK); 100 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 101 i.putExtra("album", uri.getLastPathSegment()); 102 startActivity(i); 103 finish(); 104 return; 105 } else if (path.startsWith("content://media/external/audio/artists/")) { 106 // This is an artist, show the albums for that artist 107 Intent i = new Intent(Intent.ACTION_PICK); 108 i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); 109 i.putExtra("artist", uri.getLastPathSegment()); 110 startActivity(i); 111 finish(); 112 return; 113 } 114 } 115 mFilterString = intent.getStringExtra(SearchManager.QUERY); 116 } 117 118 setContentView(R.layout.query_activity); 119 mTrackList = getListView(); 120 mTrackList.setTextFilterEnabled(true); 121 mAdapter = (QueryListAdapter) getLastNonConfigurationInstance(); 122 if (mAdapter == null) { 123 mAdapter = new QueryListAdapter( 124 getApplication(), 125 this, 126 R.layout.track_list_item, 127 null, // cursor 128 new String[] {}, 129 new int[] {}); 130 setListAdapter(mAdapter); 131 if (TextUtils.isEmpty(mFilterString)) { 132 getQueryCursor(mAdapter.getQueryHandler(), null); 133 } else { 134 mTrackList.setFilterText(mFilterString); 135 mFilterString = null; 136 } 137 } else { 138 mAdapter.setActivity(this); 139 setListAdapter(mAdapter); 140 mQueryCursor = mAdapter.getCursor(); 141 if (mQueryCursor != null) { 142 init(mQueryCursor); 143 } else { 144 getQueryCursor(mAdapter.getQueryHandler(), mFilterString); 145 } 146 } 147 } 148 149 @Override 150 public Object onRetainNonConfigurationInstance() { 151 mAdapterSent = true; 152 return mAdapter; 153 } 154 155 @Override 156 public void onPause() { 157 mReScanHandler.removeCallbacksAndMessages(null); 158 super.onPause(); 159 } 160 161 @Override 162 public void onDestroy() { 163 MusicUtils.unbindFromService(this); 164 unregisterReceiver(mScanListener); 165 super.onDestroy(); 166 if (!mAdapterSent && mAdapter != null) { 167 Cursor c = mAdapter.getCursor(); 168 if (c != null) { 169 c.close(); 170 } 171 } 172 } 173 174 /* 175 * This listener gets called when the media scanner starts up, and when the 176 * sd card is unmounted. 177 */ 178 private BroadcastReceiver mScanListener = new BroadcastReceiver() { 179 @Override 180 public void onReceive(Context context, Intent intent) { 181 MusicUtils.setSpinnerState(QueryBrowserActivity.this); 182 mReScanHandler.sendEmptyMessage(0); 183 } 184 }; 185 186 private Handler mReScanHandler = new Handler() { 187 @Override 188 public void handleMessage(Message msg) { 189 getQueryCursor(mAdapter.getQueryHandler(), null); 190 // if the query results in a null cursor, onQueryComplete() will 191 // call init(), which will post a delayed message to this handler 192 // in order to try again. 193 } 194 }; 195 196 @Override 197 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 198 switch (requestCode) { 199 case SCAN_DONE: 200 if (resultCode == RESULT_CANCELED) { 201 finish(); 202 } else { 203 getQueryCursor(mAdapter.getQueryHandler(), null); 204 } 205 break; 206 } 207 } 208 209 public void init(Cursor c) { 210 211 mAdapter.changeCursor(c); 212 213 if (mQueryCursor == null) { 214 MusicUtils.displayDatabaseError(this); 215 setListAdapter(null); 216 mReScanHandler.sendEmptyMessageDelayed(0, 1000); 217 return; 218 } 219 MusicUtils.hideDatabaseError(this); 220 } 221 222 @Override 223 protected void onListItemClick(ListView l, View v, int position, long id) 224 { 225 // Dialog doesn't allow us to wait for a result, so we need to store 226 // the info we need for when the dialog posts its result 227 mQueryCursor.moveToPosition(position); 228 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) { 229 return; 230 } 231 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow( 232 MediaStore.Audio.Media.MIME_TYPE)); 233 234 if ("artist".equals(selectedType)) { 235 Intent intent = new Intent(Intent.ACTION_PICK); 236 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); 237 intent.putExtra("artist", Long.valueOf(id).toString()); 238 startActivity(intent); 239 } else if ("album".equals(selectedType)) { 240 Intent intent = new Intent(Intent.ACTION_PICK); 241 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); 242 intent.putExtra("album", Long.valueOf(id).toString()); 243 startActivity(intent); 244 } else if (position >= 0 && id >= 0){ 245 int [] list = new int[] { (int) id }; 246 MusicUtils.playAll(this, list, 0); 247 } else { 248 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id); 249 } 250 } 251 252 @Override 253 public boolean onOptionsItemSelected(MenuItem item) { 254 switch (item.getItemId()) { 255 case USE_AS_RINGTONE: { 256 // Set the system setting to make this the current ringtone 257 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId()); 258 return true; 259 } 260 261 } 262 return super.onOptionsItemSelected(item); 263 } 264 265 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) { 266 if (filter == null) { 267 filter = ""; 268 } 269 String[] ccols = new String[] { 270 "_id", // this will be the artist, album or track ID 271 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album" 272 SearchManager.SUGGEST_COLUMN_TEXT_1, 273 "data1", 274 "data2" 275 }; 276 277 Uri search = Uri.parse("content://media/external/audio/" + 278 SearchManager.SUGGEST_URI_PATH_QUERY + "/" + Uri.encode(filter)); 279 280 Cursor ret = null; 281 if (async != null) { 282 async.startQuery(0, null, search, ccols, null, null, null); 283 } else { 284 ret = MusicUtils.query(this, search, ccols, null, null, null); 285 } 286 return ret; 287 } 288 289 static class QueryListAdapter extends SimpleCursorAdapter { 290 private QueryBrowserActivity mActivity = null; 291 private AsyncQueryHandler mQueryHandler; 292 293 class QueryHandler extends AsyncQueryHandler { 294 QueryHandler(ContentResolver res) { 295 super(res); 296 } 297 298 @Override 299 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 300 mActivity.init(cursor); 301 } 302 } 303 304 QueryListAdapter(Context context, QueryBrowserActivity currentactivity, 305 int layout, Cursor cursor, String[] from, int[] to) { 306 super(context, layout, cursor, from, to); 307 mActivity = currentactivity; 308 mQueryHandler = new QueryHandler(context.getContentResolver()); 309 } 310 311 public void setActivity(QueryBrowserActivity newactivity) { 312 mActivity = newactivity; 313 } 314 315 public AsyncQueryHandler getQueryHandler() { 316 return mQueryHandler; 317 } 318 319 @Override 320 public void bindView(View view, Context context, Cursor cursor) { 321 322 TextView tv1 = (TextView) view.findViewById(R.id.line1); 323 TextView tv2 = (TextView) view.findViewById(R.id.line2); 324 ImageView iv = (ImageView) view.findViewById(R.id.icon); 325 ViewGroup.LayoutParams p = iv.getLayoutParams(); 326 if (p == null) { 327 // seen this happen, not sure why 328 DatabaseUtils.dumpCursor(cursor); 329 return; 330 } 331 p.width = ViewGroup.LayoutParams.WRAP_CONTENT; 332 p.height = ViewGroup.LayoutParams.WRAP_CONTENT; 333 334 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow( 335 MediaStore.Audio.Media.MIME_TYPE)); 336 337 if (mimetype == null) { 338 mimetype = "audio/"; 339 } 340 if (mimetype.equals("artist")) { 341 iv.setImageResource(R.drawable.ic_mp_artist_list); 342 String name = cursor.getString(cursor.getColumnIndexOrThrow( 343 SearchManager.SUGGEST_COLUMN_TEXT_1)); 344 String displayname = name; 345 if (name.equals(MediaFile.UNKNOWN_STRING)) { 346 displayname = context.getString(R.string.unknown_artist_name); 347 } 348 tv1.setText(displayname); 349 350 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1")); 351 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2")); 352 353 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context, 354 numalbums, numsongs, name.equals(MediaFile.UNKNOWN_STRING)); 355 356 tv2.setText(songs_albums); 357 358 } else if (mimetype.equals("album")) { 359 iv.setImageResource(R.drawable.albumart_mp_unknown_list); 360 String name = cursor.getString(cursor.getColumnIndexOrThrow( 361 SearchManager.SUGGEST_COLUMN_TEXT_1)); 362 String displayname = name; 363 if (name.equals(MediaFile.UNKNOWN_STRING)) { 364 displayname = context.getString(R.string.unknown_album_name); 365 } 366 tv1.setText(displayname); 367 368 name = cursor.getString(cursor.getColumnIndexOrThrow("data1")); 369 displayname = name; 370 if (name.equals(MediaFile.UNKNOWN_STRING)) { 371 displayname = context.getString(R.string.unknown_artist_name); 372 } 373 tv2.setText(displayname); 374 375 } else if(mimetype.startsWith("audio/") || 376 mimetype.equals("application/ogg") || 377 mimetype.equals("application/x-ogg")) { 378 iv.setImageResource(R.drawable.ic_mp_song_list); 379 String name = cursor.getString(cursor.getColumnIndexOrThrow( 380 SearchManager.SUGGEST_COLUMN_TEXT_1)); 381 tv1.setText(name); 382 383 String displayname = cursor.getString(cursor.getColumnIndexOrThrow("data1")); 384 if (name.equals(MediaFile.UNKNOWN_STRING)) { 385 displayname = context.getString(R.string.unknown_artist_name); 386 } 387 name = cursor.getString(cursor.getColumnIndexOrThrow("data2")); 388 if (name.equals(MediaFile.UNKNOWN_STRING)) { 389 name = context.getString(R.string.unknown_artist_name); 390 } 391 tv2.setText(displayname + " - " + name); 392 } 393 } 394 @Override 395 public void changeCursor(Cursor cursor) { 396 if (cursor != mActivity.mQueryCursor) { 397 mActivity.mQueryCursor = cursor; 398 super.changeCursor(cursor); 399 } 400 } 401 @Override 402 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 403 return mActivity.getQueryCursor(null, constraint.toString()); 404 } 405 } 406 407 private ListView mTrackList; 408 private Cursor mQueryCursor; 409} 410 411