/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.music; import com.android.music.MusicUtils.ServiceToken; import android.app.ListActivity; import android.app.SearchManager; import android.content.AsyncQueryHandler; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.database.Cursor; import android.database.DatabaseUtils; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.provider.BaseColumns; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.ViewGroup.OnHierarchyChangeListener; import android.widget.ImageView; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; import java.util.ArrayList; public class QueryBrowserActivity extends ListActivity implements MusicUtils.Defs, ServiceConnection { private final static int PLAY_NOW = 0; private final static int ADD_TO_QUEUE = 1; private final static int PLAY_NEXT = 2; private final static int PLAY_ARTIST = 3; private final static int EXPLORE_ARTIST = 4; private final static int PLAY_ALBUM = 5; private final static int EXPLORE_ALBUM = 6; private final static int REQUERY = 3; private QueryListAdapter mAdapter; private boolean mAdapterSent; private String mFilterString = ""; private ServiceToken mToken; public QueryBrowserActivity() { } /** Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setVolumeControlStream(AudioManager.STREAM_MUSIC); mAdapter = (QueryListAdapter) getLastNonConfigurationInstance(); mToken = MusicUtils.bindToService(this, this); // defer the real work until we're bound to the service } public void onServiceConnected(ComponentName name, IBinder service) { IntentFilter f = new IntentFilter(); f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); f.addDataScheme("file"); registerReceiver(mScanListener, f); Intent intent = getIntent(); String action = intent != null ? intent.getAction() : null; if (Intent.ACTION_VIEW.equals(action)) { // this is something we got from the search bar Uri uri = intent.getData(); String path = uri.toString(); if (path.startsWith("content://media/external/audio/media/")) { // This is a specific file String id = uri.getLastPathSegment(); long [] list = new long[] { Long.valueOf(id) }; MusicUtils.playAll(this, list, 0); finish(); return; } else if (path.startsWith("content://media/external/audio/albums/")) { // This is an album, show the songs on it Intent i = new Intent(Intent.ACTION_PICK); i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); i.putExtra("album", uri.getLastPathSegment()); startActivity(i); finish(); return; } else if (path.startsWith("content://media/external/audio/artists/")) { // This is an artist, show the albums for that artist Intent i = new Intent(Intent.ACTION_PICK); i.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); i.putExtra("artist", uri.getLastPathSegment()); startActivity(i); finish(); return; } } mFilterString = intent.getStringExtra(SearchManager.QUERY); if (MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)) { String focus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS); String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST); String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM); String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE); if (focus != null) { if (focus.startsWith("audio/") && title != null) { mFilterString = title; } else if (focus.equals(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { if (album != null) { mFilterString = album; if (artist != null) { mFilterString = mFilterString + " " + artist; } } } else if (focus.equals(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { if (artist != null) { mFilterString = artist; } } } } setContentView(R.layout.query_activity); mTrackList = getListView(); mTrackList.setTextFilterEnabled(true); if (mAdapter == null) { mAdapter = new QueryListAdapter( getApplication(), this, R.layout.track_list_item, null, // cursor new String[] {}, new int[] {}); setListAdapter(mAdapter); if (TextUtils.isEmpty(mFilterString)) { getQueryCursor(mAdapter.getQueryHandler(), null); } else { mTrackList.setFilterText(mFilterString); mFilterString = null; } } else { mAdapter.setActivity(this); setListAdapter(mAdapter); mQueryCursor = mAdapter.getCursor(); if (mQueryCursor != null) { init(mQueryCursor); } else { getQueryCursor(mAdapter.getQueryHandler(), mFilterString); } } } public void onServiceDisconnected(ComponentName name) { } @Override public Object onRetainNonConfigurationInstance() { mAdapterSent = true; return mAdapter; } @Override public void onPause() { mReScanHandler.removeCallbacksAndMessages(null); super.onPause(); } @Override public void onDestroy() { MusicUtils.unbindFromService(mToken); unregisterReceiver(mScanListener); // If we have an adapter and didn't send it off to another activity yet, we should // close its cursor, which we do by assigning a null cursor to it. Doing this // instead of closing the cursor directly keeps the framework from accessing // the closed cursor later. if (!mAdapterSent && mAdapter != null) { mAdapter.changeCursor(null); } // Because we pass the adapter to the next activity, we need to make // sure it doesn't keep a reference to this activity. We can do this // by clearing its DatasetObservers, which setListAdapter(null) does. setListAdapter(null); mAdapter = null; super.onDestroy(); } /* * This listener gets called when the media scanner starts up, and when the * sd card is unmounted. */ private BroadcastReceiver mScanListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { MusicUtils.setSpinnerState(QueryBrowserActivity.this); mReScanHandler.sendEmptyMessage(0); } }; private Handler mReScanHandler = new Handler() { @Override public void handleMessage(Message msg) { if (mAdapter != null) { getQueryCursor(mAdapter.getQueryHandler(), null); } // if the query results in a null cursor, onQueryComplete() will // call init(), which will post a delayed message to this handler // in order to try again. } }; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { switch (requestCode) { case SCAN_DONE: if (resultCode == RESULT_CANCELED) { finish(); } else { getQueryCursor(mAdapter.getQueryHandler(), null); } break; } } public void init(Cursor c) { if (mAdapter == null) { return; } mAdapter.changeCursor(c); if (mQueryCursor == null) { MusicUtils.displayDatabaseError(this); setListAdapter(null); mReScanHandler.sendEmptyMessageDelayed(0, 1000); return; } MusicUtils.hideDatabaseError(this); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { // Dialog doesn't allow us to wait for a result, so we need to store // the info we need for when the dialog posts its result mQueryCursor.moveToPosition(position); if (mQueryCursor.isBeforeFirst() || mQueryCursor.isAfterLast()) { return; } String selectedType = mQueryCursor.getString(mQueryCursor.getColumnIndexOrThrow( MediaStore.Audio.Media.MIME_TYPE)); if ("artist".equals(selectedType)) { Intent intent = new Intent(Intent.ACTION_PICK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album"); intent.putExtra("artist", Long.valueOf(id).toString()); startActivity(intent); } else if ("album".equals(selectedType)) { Intent intent = new Intent(Intent.ACTION_PICK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); intent.putExtra("album", Long.valueOf(id).toString()); startActivity(intent); } else if (position >= 0 && id >= 0){ long [] list = new long[] { id }; MusicUtils.playAll(this, list, 0); } else { Log.e("QueryBrowser", "invalid position/id: " + position + "/" + id); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case USE_AS_RINGTONE: { // Set the system setting to make this the current ringtone MusicUtils.setRingtone(this, mTrackList.getSelectedItemId()); return true; } } return super.onOptionsItemSelected(item); } private Cursor getQueryCursor(AsyncQueryHandler async, String filter) { if (filter == null) { filter = ""; } String[] ccols = new String[] { BaseColumns._ID, // this will be the artist, album or track ID MediaStore.Audio.Media.MIME_TYPE, // mimetype of audio file, or "artist" or "album" MediaStore.Audio.Artists.ARTIST, MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2" }; Uri search = Uri.parse("content://media/external/audio/search/fancy/" + Uri.encode(filter)); Cursor ret = null; if (async != null) { async.startQuery(0, null, search, ccols, null, null, null); } else { ret = MusicUtils.query(this, search, ccols, null, null, null); } return ret; } static class QueryListAdapter extends SimpleCursorAdapter { private QueryBrowserActivity mActivity = null; private AsyncQueryHandler mQueryHandler; private String mConstraint = null; private boolean mConstraintIsValid = false; class QueryHandler extends AsyncQueryHandler { QueryHandler(ContentResolver res) { super(res); } @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { mActivity.init(cursor); } } QueryListAdapter(Context context, QueryBrowserActivity currentactivity, int layout, Cursor cursor, String[] from, int[] to) { super(context, layout, cursor, from, to); mActivity = currentactivity; mQueryHandler = new QueryHandler(context.getContentResolver()); } public void setActivity(QueryBrowserActivity newactivity) { mActivity = newactivity; } public AsyncQueryHandler getQueryHandler() { return mQueryHandler; } @Override public void bindView(View view, Context context, Cursor cursor) { TextView tv1 = (TextView) view.findViewById(R.id.line1); TextView tv2 = (TextView) view.findViewById(R.id.line2); ImageView iv = (ImageView) view.findViewById(R.id.icon); ViewGroup.LayoutParams p = iv.getLayoutParams(); if (p == null) { // seen this happen, not sure why DatabaseUtils.dumpCursor(cursor); return; } p.width = ViewGroup.LayoutParams.WRAP_CONTENT; p.height = ViewGroup.LayoutParams.WRAP_CONTENT; String mimetype = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.MIME_TYPE)); if (mimetype == null) { mimetype = "audio/"; } if (mimetype.equals("artist")) { iv.setImageResource(R.drawable.ic_mp_artist_list); String name = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Artists.ARTIST)); String displayname = name; boolean isunknown = false; if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) { displayname = context.getString(R.string.unknown_artist_name); isunknown = true; } tv1.setText(displayname); int numalbums = cursor.getInt(cursor.getColumnIndexOrThrow("data1")); int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow("data2")); String songs_albums = MusicUtils.makeAlbumsSongsLabel(context, numalbums, numsongs, isunknown); tv2.setText(songs_albums); } else if (mimetype.equals("album")) { iv.setImageResource(R.drawable.albumart_mp_unknown_list); String name = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Albums.ALBUM)); String displayname = name; if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) { displayname = context.getString(R.string.unknown_album_name); } tv1.setText(displayname); name = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Artists.ARTIST)); displayname = name; if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) { displayname = context.getString(R.string.unknown_artist_name); } tv2.setText(displayname); } else if(mimetype.startsWith("audio/") || mimetype.equals("application/ogg") || mimetype.equals("application/x-ogg")) { iv.setImageResource(R.drawable.ic_mp_song_list); String name = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.TITLE)); tv1.setText(name); String displayname = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Artists.ARTIST)); if (displayname == null || displayname.equals(MediaStore.UNKNOWN_STRING)) { displayname = context.getString(R.string.unknown_artist_name); } name = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Albums.ALBUM)); if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) { name = context.getString(R.string.unknown_album_name); } tv2.setText(displayname + " - " + name); } } @Override public void changeCursor(Cursor cursor) { if (mActivity.isFinishing() && cursor != null) { cursor.close(); cursor = null; } if (cursor != mActivity.mQueryCursor) { mActivity.mQueryCursor = cursor; super.changeCursor(cursor); } } @Override public Cursor runQueryOnBackgroundThread(CharSequence constraint) { String s = constraint.toString(); if (mConstraintIsValid && ( (s == null && mConstraint == null) || (s != null && s.equals(mConstraint)))) { return getCursor(); } Cursor c = mActivity.getQueryCursor(null, s); mConstraint = s; mConstraintIsValid = true; return c; } } private ListView mTrackList; private Cursor mQueryCursor; }