OSDN Git Service

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