QueryBrowserActivity.java revision 246f5a1e3edfb2e6cecfe2979bff297075edde84
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 com.android.music.MusicUtils.ServiceToken;
20
21import android.app.ListActivity;
22import android.app.SearchManager;
23import android.content.AsyncQueryHandler;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.ServiceConnection;
31
32import android.database.Cursor;
33import android.database.DatabaseUtils;
34import android.media.AudioManager;
35import android.net.Uri;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.Message;
40import android.provider.BaseColumns;
41import android.provider.MediaStore;
42import android.text.TextUtils;
43import android.util.Log;
44import android.view.KeyEvent;
45import android.view.MenuItem;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.Window;
49import android.view.ViewGroup.OnHierarchyChangeListener;
50import android.widget.ImageView;
51import android.widget.ListView;
52import android.widget.SimpleCursorAdapter;
53import android.widget.TextView;
54
55import java.util.ArrayList;
56
57public class QueryBrowserActivity extends ListActivity
58implements MusicUtils.Defs, ServiceConnection
59{
60    private final static int PLAY_NOW = 0;
61    private final static int ADD_TO_QUEUE = 1;
62    private final static int PLAY_NEXT = 2;
63    private final static int PLAY_ARTIST = 3;
64    private final static int EXPLORE_ARTIST = 4;
65    private final static int PLAY_ALBUM = 5;
66    private final static int EXPLORE_ALBUM = 6;
67    private final static int REQUERY = 3;
68    private QueryListAdapter mAdapter;
69    private boolean mAdapterSent;
70    private String mFilterString = "";
71    private ServiceToken mToken;
72
73    public QueryBrowserActivity()
74    {
75    }
76
77    /** Called when the activity is first created. */
78    @Override
79    public void onCreate(Bundle icicle)
80    {
81        super.onCreate(icicle);
82        setVolumeControlStream(AudioManager.STREAM_MUSIC);
83        mAdapter = (QueryListAdapter) getLastNonConfigurationInstance();
84        mToken = MusicUtils.bindToService(this, this);
85        // defer the real work until we're bound to the service
86    }
87
88
89    public void onServiceConnected(ComponentName name, IBinder service) {
90        IntentFilter f = new IntentFilter();
91        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
92        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
93        f.addDataScheme("file");
94        registerReceiver(mScanListener, f);
95
96        Intent intent = getIntent();
97        String action = intent != null ? intent.getAction() : null;
98
99        if (Intent.ACTION_VIEW.equals(action)) {
100            // this is something we got from the search bar
101            Uri uri = intent.getData();
102            String path = uri.toString();
103            if (path.startsWith("content://media/external/audio/media/")) {
104                // This is a specific file
105                String id = uri.getLastPathSegment();
106                long [] list = new long[] { Long.valueOf(id) };
107                MusicUtils.playAll(this, list, 0);
108                finish();
109                return;
110            } else if (path.startsWith("content://media/external/audio/albums/")) {
111                // This is an album, show the songs on it
112                Intent i = new Intent(Intent.ACTION_PICK);
113                i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
114                i.putExtra("album", uri.getLastPathSegment());
115                startActivity(i);
116                finish();
117                return;
118            } else if (path.startsWith("content://media/external/audio/artists/")) {
119                // This is an artist, show the albums for that artist
120                Intent i = new Intent(Intent.ACTION_PICK);
121                i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
122                i.putExtra("artist", uri.getLastPathSegment());
123                startActivity(i);
124                finish();
125                return;
126            }
127        }
128
129        mFilterString = intent.getStringExtra(SearchManager.QUERY);
130        if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) {
131            String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
132            String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
133            String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
134            String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
135            if (focus != null) {
136                if (focus.startsWith("audio/") && title != null) {
137                    mFilterString = title;
138                } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
139                    if (album != null) {
140                        mFilterString = album;
141                        if (artist != null) {
142                            mFilterString = mFilterString + " " + artist;
143                        }
144                    }
145                } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
146                    if (artist != null) {
147                        mFilterString = artist;
148                    }
149                }
150            }
151        }
152
153        setContentView(R.layout.query_activity);
154        mTrackList = getListView();
155        mTrackList.setTextFilterEnabled(true);
156        if (mAdapter == null) {
157            mAdapter = new QueryListAdapter(
158                    getApplication(),
159                    this,
160                    R.layout.track_list_item,
161                    null, // cursor
162                    new String[] {},
163                    new int[] {});
164            setListAdapter(mAdapter);
165            if (TextUtils.isEmpty(mFilterString)) {
166                getQueryCursor(mAdapter.getQueryHandler(), null);
167            } else {
168                mTrackList.setFilterText(mFilterString);
169                mFilterString = null;
170            }
171        } else {
172            mAdapter.setActivity(this);
173            setListAdapter(mAdapter);
174            mQueryCursor = mAdapter.getCursor();
175            if (mQueryCursor != null) {
176                init(mQueryCursor);
177            } else {
178                getQueryCursor(mAdapter.getQueryHandler(), mFilterString);
179            }
180        }
181    }
182
183    public void onServiceDisconnected(ComponentName name) {
184
185    }
186
187    @Override
188    public Object onRetainNonConfigurationInstance() {
189        mAdapterSent = true;
190        return mAdapter;
191    }
192
193    @Override
194    public void onPause() {
195        mReScanHandler.removeCallbacksAndMessages(null);
196        super.onPause();
197    }
198
199    @Override
200    public void onDestroy() {
201        MusicUtils.unbindFromService(mToken);
202        unregisterReceiver(mScanListener);
203        // If we have an adapter and didn't send it off to another activity yet, we should
204        // close its cursor, which we do by assigning a null cursor to it. Doing this
205        // instead of closing the cursor directly keeps the framework from accessing
206        // the closed cursor later.
207        if (!mAdapterSent && mAdapter != null) {
208            mAdapter.changeCursor(null);
209        }
210        // Because we pass the adapter to the next activity, we need to make
211        // sure it doesn't keep a reference to this activity. We can do this
212        // by clearing its DatasetObservers, which setListAdapter(null) does.
213        if (getListView() != null) {
214            setListAdapter(null);
215        }
216        mAdapter = null;
217        super.onDestroy();
218    }
219
220    /*
221     * This listener gets called when the media scanner starts up, and when the
222     * sd card is unmounted.
223     */
224    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
225        @Override
226        public void onReceive(Context context, Intent intent) {
227            MusicUtils.setSpinnerState(QueryBrowserActivity.this);
228            mReScanHandler.sendEmptyMessage(0);
229        }
230    };
231
232    private Handler mReScanHandler = new Handler() {
233        @Override
234        public void handleMessage(Message msg) {
235            if (mAdapter != null) {
236                getQueryCursor(mAdapter.getQueryHandler(), null);
237            }
238            // if the query results in a null cursor, onQueryComplete() will
239            // call init(), which will post a delayed message to this handler
240            // in order to try again.
241        }
242    };
243
244    @Override
245    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
246        switch (requestCode) {
247            case SCAN_DONE:
248                if (resultCode == RESULT_CANCELED) {
249                    finish();
250                } else {
251                    getQueryCursor(mAdapter.getQueryHandler(), null);
252                }
253                break;
254        }
255    }
256
257    public void init(Cursor c) {
258
259        if (mAdapter == null) {
260            return;
261        }
262        mAdapter.changeCursor(c);
263
264        if (mQueryCursor == null) {
265            MusicUtils.displayDatabaseError(this);
266            setListAdapter(null);
267            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
268            return;
269        }
270        MusicUtils.hideDatabaseError(this);
271    }
272
273    @Override
274    protected void onListItemClick(ListView l, View v, int position, long id)
275    {
276        // Dialog doesn't allow us to wait for a result, so we need to store
277        // the info we need for when the dialog posts its result
278        mQueryCursor.moveToPosition(position);
279        if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) {
280            return;
281        }
282        String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow(
283                MediaStore.Audio.Media.MIME_TYPE));
284
285        if ("artist".equals(selectedType)) {
286            Intent intent = new Intent(Intent.ACTION_PICK);
287            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
288            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
289            intent.putExtra("artist", Long.valueOf(id).toString());
290            startActivity(intent);
291        } else if ("album".equals(selectedType)) {
292            Intent intent = new Intent(Intent.ACTION_PICK);
293            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
294            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
295            intent.putExtra("album", Long.valueOf(id).toString());
296            startActivity(intent);
297        } else if (position >= 0 && id >= 0){
298            long [] list = new long[] { id };
299            MusicUtils.playAll(this, list, 0);
300        } else {
301            Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id);
302        }
303    }
304
305    @Override
306    public boolean onOptionsItemSelected(MenuItem item) {
307        switch (item.getItemId()) {
308            case USE_AS_RINGTONE: {
309                // Set the system setting to make this the current ringtone
310                MusicUtils.setRingtone(this, mTrackList.getSelectedItemId());
311                return true;
312            }
313
314        }
315        return super.onOptionsItemSelected(item);
316    }
317
318    private Cursor getQueryCursor(AsyncQueryHandler async, String filter) {
319        if (filter == null) {
320            filter = "";
321        }
322        String[] ccols = new String[] {
323                BaseColumns._ID,   // this will be the artist, album or track ID
324                MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album"
325                MediaStore.Audio.Artists.ARTIST,
326                MediaStore.Audio.Albums.ALBUM,
327                MediaStore.Audio.Media.TITLE,
328                "data1",
329                "data2"
330        };
331
332        Uri search = Uri.parse("content://media/external/audio/search/fancy/" +
333                Uri.encode(filter));
334
335        Cursor ret = null;
336        if (async != null) {
337            async.startQuery(0, null, search, ccols, null, null, null);
338        } else {
339            ret = MusicUtils.query(this, search, ccols, null, null, null);
340        }
341        return ret;
342    }
343
344    static class QueryListAdapter extends SimpleCursorAdapter {
345        private QueryBrowserActivity mActivity = null;
346        private AsyncQueryHandler mQueryHandler;
347        private String mConstraint = null;
348        private boolean mConstraintIsValid = false;
349
350        class QueryHandler extends AsyncQueryHandler {
351            QueryHandler(ContentResolver res) {
352                super(res);
353            }
354
355            @Override
356            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
357                mActivity.init(cursor);
358            }
359        }
360
361        QueryListAdapter(Context context, QueryBrowserActivity currentactivity,
362                int layout, Cursor cursor, String[] from, int[] to) {
363            super(context, layout, cursor, from, to);
364            mActivity = currentactivity;
365            mQueryHandler = new QueryHandler(context.getContentResolver());
366        }
367
368        public void setActivity(QueryBrowserActivity newactivity) {
369            mActivity = newactivity;
370        }
371
372        public AsyncQueryHandler getQueryHandler() {
373            return mQueryHandler;
374        }
375
376        @Override
377        public void bindView(View view, Context context, Cursor cursor) {
378
379            TextView tv1 = (TextView) view.findViewById(R.id.line1);
380            TextView tv2 = (TextView) view.findViewById(R.id.line2);
381            ImageView iv = (ImageView) view.findViewById(R.id.icon);
382            ViewGroup.LayoutParams p = iv.getLayoutParams();
383            if (p == null) {
384                // seen this happen, not sure why
385                DatabaseUtils.dumpCursor(cursor);
386                return;
387            }
388            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
389            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
390
391            String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(
392                    MediaStore.Audio.Media.MIME_TYPE));
393
394            if (mimetype == null) {
395                mimetype = "audio/";
396            }
397            if (mimetype.equals("artist")) {
398                iv.setImageResource(R.drawable.ic_mp_artist_list);
399                String name = cursor.getString(cursor.getColumnIndexOrThrow(
400                        MediaStore.Audio.Artists.ARTIST));
401                String displayname = name;
402                boolean isunknown = false;
403                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
404                    displayname = context.getString(R.string.unknown_artist_name);
405                    isunknown = true;
406                }
407                tv1.setText(displayname);
408
409                int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
410                int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
411
412                String songs_albums = MusicUtils.makeAlbumsSongsLabel(context,
413                        numalbums, numsongs, isunknown);
414
415                tv2.setText(songs_albums);
416
417            } else if (mimetype.equals("album")) {
418                iv.setImageResource(R.drawable.albumart_mp_unknown_list);
419                String name = cursor.getString(cursor.getColumnIndexOrThrow(
420                        MediaStore.Audio.Albums.ALBUM));
421                String displayname = name;
422                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
423                    displayname = context.getString(R.string.unknown_album_name);
424                }
425                tv1.setText(displayname);
426
427                name = cursor.getString(cursor.getColumnIndexOrThrow(
428                        MediaStore.Audio.Artists.ARTIST));
429                displayname = name;
430                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
431                    displayname = context.getString(R.string.unknown_artist_name);
432                }
433                tv2.setText(displayname);
434
435            } else if(mimetype.startsWith("audio/") ||
436                    mimetype.equals("application/ogg") ||
437                    mimetype.equals("application/x-ogg")) {
438                iv.setImageResource(R.drawable.ic_mp_song_list);
439                String name = cursor.getString(cursor.getColumnIndexOrThrow(
440                        MediaStore.Audio.Media.TITLE));
441                tv1.setText(name);
442
443                String displayname = cursor.getString(cursor.getColumnIndexOrThrow(
444                        MediaStore.Audio.Artists.ARTIST));
445                if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) {
446                    displayname = context.getString(R.string.unknown_artist_name);
447                }
448                name = cursor.getString(cursor.getColumnIndexOrThrow(
449                        MediaStore.Audio.Albums.ALBUM));
450                if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
451                    name = context.getString(R.string.unknown_album_name);
452                }
453                tv2.setText(displayname + " - " + name);
454            }
455        }
456        @Override
457        public void changeCursor(Cursor cursor) {
458            if (mActivity.isFinishing() && cursor != null) {
459                cursor.close();
460                cursor = null;
461            }
462            if (cursor != mActivity.mQueryCursor) {
463                mActivity.mQueryCursor = cursor;
464                super.changeCursor(cursor);
465            }
466        }
467        @Override
468        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
469            String s = constraint.toString();
470            if (mConstraintIsValid && (
471                    (s == null && mConstraint == null) ||
472                    (s != null && s.equals(mConstraint)))) {
473                return getCursor();
474            }
475            Cursor c = mActivity.getQueryCursor(null, s);
476            mConstraint = s;
477            mConstraintIsValid = true;
478            return c;
479        }
480    }
481
482    private ListView mTrackList;
483    private Cursor mQueryCursor;
484}
485
486