/*
* 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.R;
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;
}