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