blob: a67790a9a4f00e1e38d99109361c269c752b4fb3 [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
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;
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070023import android.content.ComponentName;
The Android Open Source Project792a2202009-03-03 19:32:30 -080024import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070028import android.content.ServiceConnection;
The Android Open Source Project792a2202009-03-03 19:32:30 -080029
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;
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070037import android.os.IBinder;
The Android Open Source Project792a2202009-03-03 19:32:30 -080038import android.os.Message;
Marco Nelissen1968e382009-05-01 09:06:07 -070039import android.provider.BaseColumns;
The Android Open Source Project792a2202009-03-03 19:32:30 -080040import 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
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070056public class QueryBrowserActivity extends ListActivity
57implements MusicUtils.Defs, ServiceConnection
The Android Open Source Project792a2202009-03-03 19:32:30 -080058{
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);
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070081 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) {
The Android Open Source Project792a2202009-03-03 19:32:30 -080088 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
Marco Nelissen3fdd97f2009-05-05 15:57:19 -070094 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 int [] list = new int[] { Integer.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;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800123 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800124 }
Marco Nelissen3fdd97f2009-05-05 15:57:19 -0700125 mFilterString = intent.getStringExtra(SearchManager.QUERY);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800126
127 setContentView(R.layout.query_activity);
128 mTrackList = getListView();
129 mTrackList.setTextFilterEnabled(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800130 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 }
Marco Nelissen3fdd97f2009-05-05 15:57:19 -0700156
157 public void onServiceDisconnected(ComponentName name) {
158
159 }
160
The Android Open Source Project792a2202009-03-03 19:32:30 -0800161 @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 }
185
186 /*
187 * This listener gets called when the media scanner starts up, and when the
188 * sd card is unmounted.
189 */
190 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
191 @Override
192 public void onReceive(Context context, Intent intent) {
193 MusicUtils.setSpinnerState(QueryBrowserActivity.this);
194 mReScanHandler.sendEmptyMessage(0);
195 }
196 };
197
198 private Handler mReScanHandler = new Handler() {
199 @Override
200 public void handleMessage(Message msg) {
201 getQueryCursor(mAdapter.getQueryHandler(), null);
202 // if the query results in a null cursor, onQueryComplete() will
203 // call init(), which will post a delayed message to this handler
204 // in order to try again.
205 }
206 };
207
208 @Override
209 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
210 switch (requestCode) {
211 case SCAN_DONE:
212 if (resultCode == RESULT_CANCELED) {
213 finish();
214 } else {
215 getQueryCursor(mAdapter.getQueryHandler(), null);
216 }
217 break;
218 }
219 }
220
221 public void init(Cursor c) {
222
223 mAdapter.changeCursor(c);
224
225 if (mQueryCursor == null) {
226 MusicUtils.displayDatabaseError(this);
227 setListAdapter(null);
228 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
229 return;
230 }
231 MusicUtils.hideDatabaseError(this);
232 }
233
234 @Override
235 protected void onListItemClick(ListView l, View v, int position, long id)
236 {
237 // Dialog doesn't allow us to wait for a result, so we need to store
238 // the info we need for when the dialog posts its result
239 mQueryCursor.moveToPosition(position);
240 if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
241 return;
242 }
243 String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
244 MediaStore.Audio.Media.MIME_TYPE));
245
246 if ("artist".equals(selectedType)) {
247 Intent intent = new Intent(Intent.ACTION_PICK);
248 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
249 intent.putExtra("artist", Long.valueOf(id).toString());
250 startActivity(intent);
251 } else if ("album".equals(selectedType)) {
252 Intent intent = new Intent(Intent.ACTION_PICK);
253 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
254 intent.putExtra("album", Long.valueOf(id).toString());
255 startActivity(intent);
256 } else if (position >= 0 && id >= 0){
257 int [] list = new int[] { (int) id };
258 MusicUtils.playAll(this, list, 0);
259 } else {
260 Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
261 }
262 }
263
264 @Override
265 public boolean onOptionsItemSelected(MenuItem item) {
266 switch (item.getItemId()) {
267 case USE_AS_RINGTONE: {
268 // Set the system setting to make this the current ringtone
269 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
270 return true;
271 }
272
273 }
274 return super.onOptionsItemSelected(item);
275 }
276
277 private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
278 if (filter == null) {
279 filter = "";
280 }
281 String[] ccols = new String[] {
Marco Nelissen1968e382009-05-01 09:06:07 -0700282 BaseColumns._ID, // this will be the artist, album or track ID
The Android Open Source Project792a2202009-03-03 19:32:30 -0800283 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
Marco Nelissen1968e382009-05-01 09:06:07 -0700284 MediaStore.Audio.Artists.ARTIST,
285 MediaStore.Audio.Albums.ALBUM,
286 MediaStore.Audio.Media.TITLE,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800287 "data1",
288 "data2"
289 };
290
Marco Nelissen1968e382009-05-01 09:06:07 -0700291 Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
292 Uri.encode(filter));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800293
294 Cursor ret = null;
295 if (async != null) {
296 async.startQuery(0, null, search, ccols, null, null, null);
297 } else {
298 ret = MusicUtils.query(this, search, ccols, null, null, null);
299 }
300 return ret;
301 }
302
303 static class QueryListAdapter extends SimpleCursorAdapter {
304 private QueryBrowserActivity mActivity = null;
305 private AsyncQueryHandler mQueryHandler;
306 private String mConstraint = null;
307 private boolean mConstraintIsValid = false;
308
309 class QueryHandler extends AsyncQueryHandler {
310 QueryHandler(ContentResolver res) {
311 super(res);
312 }
313
314 @Override
315 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
316 mActivity.init(cursor);
317 }
318 }
319
320 QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
321 int layout, Cursor cursor, String[] from, int[] to) {
322 super(context, layout, cursor, from, to);
323 mActivity = currentactivity;
324 mQueryHandler = new QueryHandler(context.getContentResolver());
325 }
326
327 public void setActivity(QueryBrowserActivity newactivity) {
328 mActivity = newactivity;
329 }
330
331 public AsyncQueryHandler getQueryHandler() {
332 return mQueryHandler;
333 }
334
335 @Override
336 public void bindView(View view, Context context, Cursor cursor) {
337
338 TextView tv1 = (TextView) view.findViewById(R.id.line1);
339 TextView tv2 = (TextView) view.findViewById(R.id.line2);
340 ImageView iv = (ImageView) view.findViewById(R.id.icon);
341 ViewGroup.LayoutParams p = iv.getLayoutParams();
342 if (p == null) {
343 // seen this happen, not sure why
344 DatabaseUtils.dumpCursor(cursor);
345 return;
346 }
347 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
348 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
349
350 String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
351 MediaStore.Audio.Media.MIME_TYPE));
352
353 if (mimetype == null) {
354 mimetype = "audio/";
355 }
356 if (mimetype.equals("artist")) {
357 iv.setImageResource(R.drawable.ic_mp_artist_list);
358 String name = cursor.getString(cursor.getColumnIndexOrThrow(
Marco Nelissen1968e382009-05-01 09:06:07 -0700359 MediaStore.Audio.Artists.ARTIST));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800360 String displayname = name;
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700361 boolean isunknown = false;
362 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800363 displayname = context.getString(R.string.unknown_artist_name);
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700364 isunknown = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800365 }
366 tv1.setText(displayname);
367
368 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
369 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
370
371 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700372 numalbums, numsongs, isunknown);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800373
374 tv2.setText(songs_albums);
375
376 } else if (mimetype.equals("album")) {
377 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
378 String name = cursor.getString(cursor.getColumnIndexOrThrow(
Marco Nelissen1968e382009-05-01 09:06:07 -0700379 MediaStore.Audio.Albums.ALBUM));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800380 String displayname = name;
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700381 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800382 displayname = context.getString(R.string.unknown_album_name);
383 }
384 tv1.setText(displayname);
385
Marco Nelissen1968e382009-05-01 09:06:07 -0700386 name = cursor.getString(cursor.getColumnIndexOrThrow(
387 MediaStore.Audio.Artists.ARTIST));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800388 displayname = name;
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700389 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800390 displayname = context.getString(R.string.unknown_artist_name);
391 }
392 tv2.setText(displayname);
393
394 } else if(mimetype.startsWith("audio/") ||
395 mimetype.equals("application/ogg") ||
396 mimetype.equals("application/x-ogg")) {
397 iv.setImageResource(R.drawable.ic_mp_song_list);
398 String name = cursor.getString(cursor.getColumnIndexOrThrow(
Marco Nelissen1968e382009-05-01 09:06:07 -0700399 MediaStore.Audio.Media.TITLE));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800400 tv1.setText(name);
401
Marco Nelissen1968e382009-05-01 09:06:07 -0700402 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
403 MediaStore.Audio.Artists.ARTIST));
404 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800405 displayname = context.getString(R.string.unknown_artist_name);
406 }
Marco Nelissen1968e382009-05-01 09:06:07 -0700407 name = cursor.getString(cursor.getColumnIndexOrThrow(
408 MediaStore.Audio.Albums.ALBUM));
The Android Open Source Project6a9c41c2009-03-09 11:52:14 -0700409 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
Marco Nelissen1968e382009-05-01 09:06:07 -0700410 name = context.getString(R.string.unknown_album_name);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800411 }
412 tv2.setText(displayname + " - " + name);
413 }
414 }
415 @Override
416 public void changeCursor(Cursor cursor) {
417 if (cursor != mActivity.mQueryCursor) {
418 mActivity.mQueryCursor = cursor;
419 super.changeCursor(cursor);
420 }
421 }
422 @Override
423 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
424 String s = constraint.toString();
425 if (mConstraintIsValid && (
426 (s == null && mConstraint == null) ||
427 (s != null && s.equals(mConstraint)))) {
428 return getCursor();
429 }
430 Cursor c = mActivity.getQueryCursor(null, s);
431 mConstraint = s;
432 mConstraintIsValid = true;
433 return c;
434 }
435 }
436
437 private ListView mTrackList;
438 private Cursor mQueryCursor;
439}
440