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