QueryBrowserActivity.java revision 42bcc218ce4c330cc609326d168e288f3559c64f
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.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29
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;
37import android.os.IBinder;
38import android.os.Message;
39import android.provider.BaseColumns;
40import 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
56public class QueryBrowserActivity extends ListActivity
57implements 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
477