OSDN Git Service

Fix a whole bunch of monkey bugs by making sure that the adapter
[android-x86/packages-apps-Music.git] / src / com / android / music / QueryBrowserActivity.java
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
17 package com.android.music;
18
19 import android.app.ListActivity;
20 import android.app.SearchManager;
21 import android.content.AsyncQueryHandler;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29
30 import android.database.Cursor;
31 import android.database.DatabaseUtils;
32 import android.media.AudioManager;
33 import android.media.MediaFile;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.Message;
39 import android.provider.BaseColumns;
40 import android.provider.MediaStore;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyEvent;
44 import android.view.MenuItem;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.Window;
48 import android.view.ViewGroup.OnHierarchyChangeListener;
49 import android.widget.ImageView;
50 import android.widget.ListView;
51 import android.widget.SimpleCursorAdapter;
52 import android.widget.TextView;
53
54 import java.util.ArrayList;
55
56 public class QueryBrowserActivity extends ListActivity
57 implements 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         // If we have an adapter and didn't send it off to another activity yet, we should
202         // close its cursor, which we do by assigning a null cursor to it. Doing this
203         // instead of closing the cursor directly keeps the framework from accessing
204         // the closed cursor later.
205         if (!mAdapterSent && mAdapter != null) {
206             mAdapter.changeCursor(null);
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         super.onDestroy();
214     }
215     
216     /*
217      * This listener gets called when the media scanner starts up, and when the
218      * sd card is unmounted.
219      */
220     private BroadcastReceiver mScanListener = new BroadcastReceiver() {
221         @Override
222         public void onReceive(Context context, Intent intent) {
223             MusicUtils.setSpinnerState(QueryBrowserActivity.this);
224             mReScanHandler.sendEmptyMessage(0);
225         }
226     };
227     
228     private Handler mReScanHandler = new Handler() {
229         @Override
230         public void handleMessage(Message msg) {
231             if (mAdapter != null) {
232                 getQueryCursor(mAdapter.getQueryHandler(), null);
233             }
234             // if the query results in a null cursor, onQueryComplete() will
235             // call init(), which will post a delayed message to this handler
236             // in order to try again.
237         }
238     };
239
240     @Override
241     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
242         switch (requestCode) {
243             case SCAN_DONE:
244                 if (resultCode == RESULT_CANCELED) {
245                     finish();
246                 } else {
247                     getQueryCursor(mAdapter.getQueryHandler(), null);
248                 }
249                 break;
250         }
251     }
252     
253     public void init(Cursor c) {
254
255         if (mAdapter == null) {
256             return;
257         }
258         mAdapter.changeCursor(c);
259
260         if (mQueryCursor == null) {
261             MusicUtils.displayDatabaseError(this);
262             setListAdapter(null);
263             mReScanHandler.sendEmptyMessageDelayed(0, 1000);
264             return;
265         }
266         MusicUtils.hideDatabaseError(this);
267     }
268     
269     @Override
270     protected void onListItemClick(ListView l, View v, int position, long id)
271     {
272         // Dialog doesn't allow us to wait for a result, so we need to store
273         // the info we need for when the dialog posts its result
274         mQueryCursor.moveToPosition(position);
275         if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
276             return;
277         }
278         String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
279                 MediaStore.Audio.Media.MIME_TYPE));
280         
281         if ("artist".equals(selectedType)) {
282             Intent intent = new Intent(Intent.ACTION_PICK);
283             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
284             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
285             intent.putExtra("artist", Long.valueOf(id).toString());
286             startActivity(intent);
287         } else if ("album".equals(selectedType)) {
288             Intent intent = new Intent(Intent.ACTION_PICK);
289             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
290             intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
291             intent.putExtra("album", Long.valueOf(id).toString());
292             startActivity(intent);
293         } else if (position >= 0 && id >= 0){
294             long [] list = new long[] { id };
295             MusicUtils.playAll(this, list, 0);
296         } else {
297             Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
298         }
299     }
300
301     @Override
302     public boolean onOptionsItemSelected(MenuItem item) {
303         switch (item.getItemId()) {
304             case USE_AS_RINGTONE: {
305                 // Set the system setting to make this the current ringtone
306                 MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
307                 return true;
308             }
309
310         }
311         return super.onOptionsItemSelected(item);
312     }
313
314     private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
315         if (filter == null) {
316             filter = "";
317         }
318         String[] ccols = new String[] {
319                 BaseColumns._ID,   // this will be the artist, album or track ID
320                 MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
321                 MediaStore.Audio.Artists.ARTIST,
322                 MediaStore.Audio.Albums.ALBUM,
323                 MediaStore.Audio.Media.TITLE,
324                 "data1",
325                 "data2"
326         };
327
328         Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
329                 Uri.encode(filter));
330         
331         Cursor ret = null;
332         if (async != null) {
333             async.startQuery(0, null, search, ccols, null, null, null);
334         } else {
335             ret = MusicUtils.query(this, search, ccols, null, null, null);
336         }
337         return ret;
338     }
339     
340     static class QueryListAdapter extends SimpleCursorAdapter {
341         private QueryBrowserActivity mActivity = null;
342         private AsyncQueryHandler mQueryHandler;
343         private String mConstraint = null;
344         private boolean mConstraintIsValid = false;
345
346         class QueryHandler extends AsyncQueryHandler {
347             QueryHandler(ContentResolver res) {
348                 super(res);
349             }
350             
351             @Override
352             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
353                 mActivity.init(cursor);
354             }
355         }
356
357         QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
358                 int layout, Cursor cursor, String[] from, int[] to) {
359             super(context, layout, cursor, from, to);
360             mActivity = currentactivity;
361             mQueryHandler = new QueryHandler(context.getContentResolver());
362         }
363
364         public void setActivity(QueryBrowserActivity newactivity) {
365             mActivity = newactivity;
366         }
367         
368         public AsyncQueryHandler getQueryHandler() {
369             return mQueryHandler;
370         }
371
372         @Override
373         public void bindView(View view, Context context, Cursor cursor) {
374             
375             TextView tv1 = (TextView) view.findViewById(R.id.line1);
376             TextView tv2 = (TextView) view.findViewById(R.id.line2);
377             ImageView iv = (ImageView) view.findViewById(R.id.icon);
378             ViewGroup.LayoutParams p = iv.getLayoutParams();
379             if (p == null) {
380                 // seen this happen, not sure why
381                 DatabaseUtils.dumpCursor(cursor);
382                 return;
383             }
384             p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
385             p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
386             
387             String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
388                     MediaStore.Audio.Media.MIME_TYPE));
389             
390             if (mimetype == null) {
391                 mimetype = "audio/";
392             }
393             if (mimetype.equals("artist")) {
394                 iv.setImageResource(R.drawable.ic_mp_artist_list);
395                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
396                         MediaStore.Audio.Artists.ARTIST));
397                 String displayname = name;
398                 boolean isunknown = false;
399                 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
400                     displayname = context.getString(R.string.unknown_artist_name);
401                     isunknown = true;
402                 }
403                 tv1.setText(displayname);
404
405                 int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
406                 int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
407                 
408                 String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
409                         numalbums, numsongs, isunknown);
410                 
411                 tv2.setText(songs_albums);
412             
413             } else if (mimetype.equals("album")) {
414                 iv.setImageResource(R.drawable.albumart_mp_unknown_list);
415                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
416                         MediaStore.Audio.Albums.ALBUM));
417                 String displayname = name;
418                 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
419                     displayname = context.getString(R.string.unknown_album_name);
420                 }
421                 tv1.setText(displayname);
422                 
423                 name = cursor.getString(cursor.getColumnIndexOrThrow(
424                         MediaStore.Audio.Artists.ARTIST));
425                 displayname = name;
426                 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
427                     displayname = context.getString(R.string.unknown_artist_name);
428                 }
429                 tv2.setText(displayname);
430                 
431             } else if(mimetype.startsWith("audio/") ||
432                     mimetype.equals("application/ogg") ||
433                     mimetype.equals("application/x-ogg")) {
434                 iv.setImageResource(R.drawable.ic_mp_song_list);
435                 String name = cursor.getString(cursor.getColumnIndexOrThrow(
436                         MediaStore.Audio.Media.TITLE));
437                 tv1.setText(name);
438
439                 String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
440                         MediaStore.Audio.Artists.ARTIST));
441                 if (displayname == null || displayname.equals(MediaFile.UNKNOWN_STRING)) {
442                     displayname = context.getString(R.string.unknown_artist_name);
443                 }
444                 name = cursor.getString(cursor.getColumnIndexOrThrow(
445                         MediaStore.Audio.Albums.ALBUM));
446                 if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
447                     name = context.getString(R.string.unknown_album_name);
448                 }
449                 tv2.setText(displayname + " - " + name);
450             }
451         }
452         @Override
453         public void changeCursor(Cursor cursor) {
454             if (mActivity.isFinishing()) {
455                 return;
456             }
457             if (cursor != mActivity.mQueryCursor) {
458                 mActivity.mQueryCursor = cursor;
459                 super.changeCursor(cursor);
460             }
461         }
462         @Override
463         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
464             String s = constraint.toString();
465             if (mConstraintIsValid && (
466                     (s == null && mConstraint == null) ||
467                     (s != null && s.equals(mConstraint)))) {
468                 return getCursor();
469             }
470             Cursor c = mActivity.getQueryCursor(null, s);
471             mConstraint = s;
472             mConstraintIsValid = true;
473             return c;
474         }
475     }
476
477     private ListView mTrackList;
478     private Cursor mQueryCursor;
479 }
480