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