blob: 69d6185678bc635e7b622af0279e4f78d5bf6c3f [file] [log] [blame]
/*
* 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.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
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.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.Adapter;
import android.widget.AlphabetIndexer;
import android.widget.CursorAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import java.text.Collator;
public class AlbumBrowserActivity extends ListActivity
implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
{
private String mCurrentAlbumId;
private String mCurrentAlbumName;
private String mCurrentArtistNameForAlbum;
boolean mIsUnknownArtist;
boolean mIsUnknownAlbum;
private AlbumListAdapter mAdapter;
private boolean mAdapterSent;
private final static int SEARCH = CHILD_MENU_BASE;
private static int mLastListPosCourse = -1;
private static int mLastListPosFine = -1;
private ServiceToken mToken;
public AlbumBrowserActivity()
{
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle)
{
if (icicle != null) {
mCurrentAlbumId = icicle.getString("selectedalbum");
mArtistId = icicle.getString("artist");
} else {
mArtistId = getIntent().getStringExtra("artist");
}
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mToken = MusicUtils.bindToService(this, this);
IntentFilter f = new IntentFilter();
f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
f.addDataScheme("file");
registerReceiver(mScanListener, f);
setContentView(R.layout.media_picker_activity);
MusicUtils.updateButtonBar(this, R.id.albumtab);
ListView lv = getListView();
lv.setOnCreateContextMenuListener(this);
lv.setTextFilterEnabled(true);
mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance();
if (mAdapter == null) {
//Log.i("@@@", "starting query");
mAdapter = new AlbumListAdapter(
getApplication(),
this,
R.layout.track_list_item,
mAlbumCursor,
new String[] {},
new int[] {});
setListAdapter(mAdapter);
setTitle(R.string.working_albums);
getAlbumCursor(mAdapter.getQueryHandler(), null);
} else {
mAdapter.setActivity(this);
setListAdapter(mAdapter);
mAlbumCursor = mAdapter.getCursor();
if (mAlbumCursor != null) {
init(mAlbumCursor);
} else {
getAlbumCursor(mAdapter.getQueryHandler(), null);
}
}
}
@Override
public Object onRetainNonConfigurationInstance() {
mAdapterSent = true;
return mAdapter;
}
@Override
public void onSaveInstanceState(Bundle outcicle) {
// need to store the selected item so we don't lose it in case
// of an orientation switch. Otherwise we could lose it while
// in the middle of specifying a playlist to add the item to.
outcicle.putString("selectedalbum", mCurrentAlbumId);
outcicle.putString("artist", mArtistId);
super.onSaveInstanceState(outcicle);
}
@Override
public void onDestroy() {
ListView lv = getListView();
if (lv != null) {
mLastListPosCourse = lv.getFirstVisiblePosition();
View cv = lv.getChildAt(0);
if (cv != null) {
mLastListPosFine = cv.getTop();
}
}
MusicUtils.unbindFromService(mToken);
// 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;
unregisterReceiver(mScanListener);
super.onDestroy();
}
@Override
public void onResume() {
super.onResume();
IntentFilter f = new IntentFilter();
f.addAction(MediaPlaybackService.META_CHANGED);
f.addAction(MediaPlaybackService.QUEUE_CHANGED);
registerReceiver(mTrackListListener, f);
mTrackListListener.onReceive(null, null);
MusicUtils.setSpinnerState(this);
}
private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
getListView().invalidateViews();
MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
}
};
private BroadcastReceiver mScanListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
mReScanHandler.sendEmptyMessage(0);
if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
MusicUtils.clearAlbumArtCache();
}
}
};
private Handler mReScanHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (mAdapter != null) {
getAlbumCursor(mAdapter.getQueryHandler(), null);
}
}
};
@Override
public void onPause() {
unregisterReceiver(mTrackListListener);
mReScanHandler.removeCallbacksAndMessages(null);
super.onPause();
}
public void init(Cursor c) {
if (mAdapter == null) {
return;
}
mAdapter.changeCursor(c); // also sets mAlbumCursor
if (mAlbumCursor == null) {
MusicUtils.displayDatabaseError(this);
closeContextMenu();
mReScanHandler.sendEmptyMessageDelayed(0, 1000);
return;
}
// restore previous position
if (mLastListPosCourse >= 0) {
getListView().setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
mLastListPosCourse = -1;
}
MusicUtils.hideDatabaseError(this);
MusicUtils.updateButtonBar(this, R.id.albumtab);
setTitle();
}
private void setTitle() {
CharSequence fancyName = "";
if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
mAlbumCursor.moveToFirst();
fancyName = mAlbumCursor.getString(
mAlbumCursor.getColumnIndex(MediaStore.Audio.Albums.ARTIST));
if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING))
fancyName = getText(R.string.unknown_artist_name);
}
if (mArtistId != null && fancyName != null)
setTitle(fancyName);
else
setTitle(R.string.albums_title);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
MusicUtils.makePlaylistMenu(this, sub);
menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
mAlbumCursor.moveToPosition(mi.position);
mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
mCurrentArtistNameForAlbum = mAlbumCursor.getString(
mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
mIsUnknownArtist = mCurrentArtistNameForAlbum == null ||
mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
mIsUnknownAlbum = mCurrentAlbumName == null ||
mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
if (mIsUnknownAlbum) {
menu.setHeaderTitle(getString(R.string.unknown_album_name));
} else {
menu.setHeaderTitle(mCurrentAlbumName);
}
if (!mIsUnknownAlbum || !mIsUnknownArtist) {
menu.add(0, SEARCH, 0, R.string.search_title);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case PLAY_SELECTION: {
// play the selected album
long [] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
MusicUtils.playAll(this, list, 0);
return true;
}
case QUEUE: {
long [] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
MusicUtils.addToCurrentPlaylist(this, list);
return true;
}
case NEW_PLAYLIST: {
Intent intent = new Intent();
intent.setClass(this, CreatePlaylist.class);
startActivityForResult(intent, NEW_PLAYLIST);
return true;
}
case PLAYLIST_SELECTED: {
long [] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
long playlist = item.getIntent().getLongExtra("playlist", 0);
MusicUtils.addToPlaylist(this, list, playlist);
return true;
}
case DELETE_ITEM: {
long [] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
String f;
if (android.os.Environment.isExternalStorageRemovable()) {
f = getString(R.string.delete_album_desc);
} else {
f = getString(R.string.delete_album_desc_nosdcard);
}
String desc = String.format(f, mCurrentAlbumName);
Bundle b = new Bundle();
b.putString("description", desc);
b.putLongArray("items", list);
Intent intent = new Intent();
intent.setClass(this, DeleteItems.class);
intent.putExtras(b);
startActivityForResult(intent, -1);
return true;
}
case SEARCH:
doSearch();
return true;
}
return super.onContextItemSelected(item);
}
void doSearch() {
CharSequence title = null;
String query = "";
Intent i = new Intent();
i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
title = "";
if (!mIsUnknownAlbum) {
query = mCurrentAlbumName;
i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
title = mCurrentAlbumName;
}
if(!mIsUnknownArtist) {
query = query + " " + mCurrentArtistNameForAlbum;
i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
title = title + " " + mCurrentArtistNameForAlbum;
}
// Since we hide the 'search' menu item when both album and artist are
// unknown, the query and title strings will have at least one of those.
i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
title = getString(R.string.mediasearch, title);
i.putExtra(SearchManager.QUERY, query);
startActivity(Intent.createChooser(i, title));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case SCAN_DONE:
if (resultCode == RESULT_CANCELED) {
finish();
} else {
getAlbumCursor(mAdapter.getQueryHandler(), null);
}
break;
case NEW_PLAYLIST:
if (resultCode == RESULT_OK) {
Uri uri = intent.getData();
if (uri != null) {
long [] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
MusicUtils.addToPlaylist(this, list, Long.parseLong(uri.getLastPathSegment()));
}
}
break;
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id)
{
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
intent.putExtra("album", Long.valueOf(id).toString());
intent.putExtra("artist", mArtistId);
startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MusicUtils.setPartyShuffleMenuIcon(menu);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
Cursor cursor;
switch (item.getItemId()) {
case PARTY_SHUFFLE:
MusicUtils.togglePartyShuffle();
break;
case SHUFFLE_ALL:
cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String [] { MediaStore.Audio.Media._ID},
MediaStore.Audio.Media.IS_MUSIC + "=1", null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
if (cursor != null) {
MusicUtils.shuffleAll(this, cursor);
cursor.close();
}
return true;
}
return super.onOptionsItemSelected(item);
}
private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) {
String[] cols = new String[] {
MediaStore.Audio.Albums._ID,
MediaStore.Audio.Albums.ARTIST,
MediaStore.Audio.Albums.ALBUM,
MediaStore.Audio.Albums.ALBUM_ART
};
Cursor ret = null;
if (mArtistId != null) {
Uri uri = MediaStore.Audio.Artists.Albums.getContentUri("external",
Long.valueOf(mArtistId));
if (!TextUtils.isEmpty(filter)) {
uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
}
if (async != null) {
async.startQuery(0, null, uri,
cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
} else {
ret = MusicUtils.query(this, uri,
cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
}
} else {
Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
if (!TextUtils.isEmpty(filter)) {
uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
}
if (async != null) {
async.startQuery(0, null,
uri,
cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
} else {
ret = MusicUtils.query(this, uri,
cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
}
}
return ret;
}
static class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer {
private final Drawable mNowPlayingOverlay;
private final BitmapDrawable mDefaultAlbumIcon;
private int mAlbumIdx;
private int mArtistIdx;
private int mAlbumArtIndex;
private final Resources mResources;
private final StringBuilder mStringBuilder = new StringBuilder();
private final String mUnknownAlbum;
private final String mUnknownArtist;
private final String mAlbumSongSeparator;
private final Object[] mFormatArgs = new Object[1];
private AlphabetIndexer mIndexer;
private AlbumBrowserActivity mActivity;
private AsyncQueryHandler mQueryHandler;
private String mConstraint = null;
private boolean mConstraintIsValid = false;
static class ViewHolder {
TextView line1;
TextView line2;
ImageView play_indicator;
ImageView icon;
}
class QueryHandler extends AsyncQueryHandler {
QueryHandler(ContentResolver res) {
super(res);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
//Log.i("@@@", "query complete");
mActivity.init(cursor);
}
}
AlbumListAdapter(Context context, AlbumBrowserActivity currentactivity,
int layout, Cursor cursor, String[] from, int[] to) {
super(context, layout, cursor, from, to);
mActivity = currentactivity;
mQueryHandler = new QueryHandler(context.getContentResolver());
mUnknownAlbum = context.getString(R.string.unknown_album_name);
mUnknownArtist = context.getString(R.string.unknown_artist_name);
mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
Resources r = context.getResources();
mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
mDefaultAlbumIcon = new BitmapDrawable(context.getResources(), b);
// no filter or dither, it's a lot faster and we can't tell the difference
mDefaultAlbumIcon.setFilterBitmap(false);
mDefaultAlbumIcon.setDither(false);
getColumnIndices(cursor);
mResources = context.getResources();
}
private void getColumnIndices(Cursor cursor) {
if (cursor != null) {
mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
if (mIndexer != null) {
mIndexer.setCursor(cursor);
} else {
mIndexer = new MusicAlphabetIndexer(cursor, mAlbumIdx, mResources.getString(
R.string.fast_scroll_alphabet));
}
}
}
public void setActivity(AlbumBrowserActivity newactivity) {
mActivity = newactivity;
}
public AsyncQueryHandler getQueryHandler() {
return mQueryHandler;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = super.newView(context, cursor, parent);
ViewHolder vh = new ViewHolder();
vh.line1 = (TextView) v.findViewById(R.id.line1);
vh.line2 = (TextView) v.findViewById(R.id.line2);
vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
vh.icon = (ImageView) v.findViewById(R.id.icon);
vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
vh.icon.setPadding(0, 0, 1, 0);
v.setTag(vh);
return v;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder vh = (ViewHolder) view.getTag();
String name = cursor.getString(mAlbumIdx);
String displayname = name;
boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING);
if (unknown) {
displayname = mUnknownAlbum;
}
vh.line1.setText(displayname);
name = cursor.getString(mArtistIdx);
displayname = name;
if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
displayname = mUnknownArtist;
}
vh.line2.setText(displayname);
ImageView iv = vh.icon;
// We don't actually need the path to the thumbnail file,
// we just use it to see if there is album art or not
String art = cursor.getString(mAlbumArtIndex);
long aid = cursor.getLong(0);
if (unknown || art == null || art.length() == 0) {
iv.setImageDrawable(null);
} else {
Drawable d = MusicUtils.getCachedArtwork(context, aid, mDefaultAlbumIcon);
iv.setImageDrawable(d);
}
long currentalbumid = MusicUtils.getCurrentAlbumId();
iv = vh.play_indicator;
if (currentalbumid == aid) {
iv.setImageDrawable(mNowPlayingOverlay);
} else {
iv.setImageDrawable(null);
}
}
@Override
public void changeCursor(Cursor cursor) {
if (mActivity.isFinishing() && cursor != null) {
cursor.close();
cursor = null;
}
if (cursor != mActivity.mAlbumCursor) {
mActivity.mAlbumCursor = cursor;
getColumnIndices(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.getAlbumCursor(null, s);
mConstraint = s;
mConstraintIsValid = true;
return c;
}
public Object[] getSections() {
return mIndexer.getSections();
}
public int getPositionForSection(int section) {
return mIndexer.getPositionForSection(section);
}
public int getSectionForPosition(int position) {
return 0;
}
}
private Cursor mAlbumCursor;
private String mArtistId;
public void onServiceConnected(ComponentName name, IBinder service) {
MusicUtils.updateNowPlaying(this);
}
public void onServiceDisconnected(ComponentName name) {
finish();
}
}