2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.music;
19 import java.util.HashSet;
21 import android.app.ListActivity;
22 import android.app.SearchManager;
23 import android.content.AsyncQueryHandler;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.ServiceConnection;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager;
33 import android.content.pm.PackageManager.NameNotFoundException;
34 import android.content.res.Configuration;
35 import android.content.res.Resources;
36 import android.database.Cursor;
37 import android.database.sqlite.SQLiteException;
38 import android.graphics.Bitmap;
39 import android.graphics.BitmapFactory;
40 import android.graphics.drawable.AnimationDrawable;
41 import android.graphics.drawable.BitmapDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.media.AudioManager;
44 import android.net.Uri;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.Message;
49 import android.os.RemoteException;
50 import android.provider.BaseColumns;
51 import android.provider.MediaStore;
52 import android.provider.MediaStore.Audio.AlbumColumns;
53 import android.provider.MediaStore.Audio.AudioColumns;
54 import android.text.TextUtils;
55 import android.util.Log;
56 import android.util.SparseBooleanArray;
57 import android.view.ActionMode;
58 import android.view.ContextMenu;
59 import android.view.ContextMenu.ContextMenuInfo;
60 import android.view.GestureDetector;
61 import android.view.GestureDetector.SimpleOnGestureListener;
62 import android.view.KeyEvent;
63 import android.view.LayoutInflater;
64 import android.view.Menu;
65 import android.view.MenuInflater;
66 import android.view.MenuItem;
67 import android.view.MotionEvent;
68 import android.view.SubMenu;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.Window;
72 import android.widget.AdapterView.AdapterContextMenuInfo;
73 import android.widget.AlphabetIndexer;
74 import android.widget.FrameLayout;
75 import android.widget.ImageButton;
76 import android.widget.ImageView;
77 import android.widget.LinearLayout;
78 import android.widget.ListView;
79 import android.widget.ProgressBar;
80 import android.widget.RelativeLayout;
81 import android.widget.SectionIndexer;
82 import android.widget.SimpleCursorAdapter;
83 import android.widget.TabWidget;
84 import android.widget.TextView;
86 import com.android.music.MusicUtils.ServiceToken;
88 public class AlbumBrowserActivity extends ListActivity implements
89 View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
90 private String mCurrentAlbumId;
91 private String mCurrentAlbumName;
92 private String mCurrentArtistNameForAlbum;
93 boolean mIsUnknownArtist;
94 boolean mIsUnknownAlbum;
95 private AlbumListAdapter mAdapter;
96 private boolean mAdapterSent;
97 private final static int SEARCH = CHILD_MENU_BASE;
98 private static int mLastListPosCourse = -1;
99 private static int mLastListPosFine = -1;
100 private ServiceToken mToken;
102 private IMediaPlaybackService mService = null;
103 private SharedPreferences mPreferences;
105 public LinearLayout mAlbumTab;
106 public TabWidget mButtonBar;
107 public TextView mButtonBarArtist;
108 public TextView mButtonBarAlbum;
109 public TextView mButtonBarSong;
110 public TextView mButtonBarPlaylist;
111 public TextView mButtonBarNP;
112 public RelativeLayout mGroup;
113 public RelativeLayout mChild;
114 // Smaller now playing window buttons
115 private LinearLayout mNowPlaying;
116 private ImageView mInfoDivider;
117 private ProgressBar mProgress;
118 private ImageButton mDoSearch;
119 private ImageButton mMarket;
120 private ImageButton mNext;
121 private ImageButton mPlay;
122 private ImageButton mPlusPlaylist;
123 private ImageButton mPrev;
124 private ImageButton mShare;
125 private ImageButton mFlow;
126 // Back button long press
127 public String back_button_db;
128 // Smaller now playing window swipe gesture
129 public GestureDetector gestureDetector;
130 private static final int SWIPE_MIN_DISTANCE = 120;
131 private static final int SWIPE_MAX_OFF_PATH = 250;
132 private static final int SWIPE_THRESHOLD_VELOCITY = 200;
133 private String np_swipe_gesture_db;
135 private GestureDetector swipetabs;
136 // ADW Theme constants
137 public static final int THEME_ITEM_BACKGROUND = 0;
138 public static final int THEME_ITEM_FOREGROUND = 1;
139 public static final int THEME_ITEM_TEXT_DRAWABLE = 2;
141 public AlbumBrowserActivity() {
144 /** Called when the activity is first created. */
146 public void onCreate(Bundle icicle) {
147 if (icicle != null) {
148 mCurrentAlbumId = icicle.getString("selectedalbum");
149 mArtistId = icicle.getString("artist");
151 mArtistId = getIntent().getStringExtra("artist");
153 super.onCreate(icicle);
154 mPreferences = getSharedPreferences(
155 MusicSettingsActivity.PREFERENCES_FILE, MODE_PRIVATE);
157 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
158 requestWindowFeature(Window.FEATURE_NO_TITLE);
159 setVolumeControlStream(AudioManager.STREAM_MUSIC);
161 IntentFilter f = new IntentFilter();
162 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
163 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
164 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
165 f.addDataScheme("file");
166 registerReceiver(mScanListener, f);
168 setContentView(R.layout.media_picker_activity);
170 MusicUtils.updateButtonBar(this, R.id.albumtab);
171 MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
173 lv = (ListView) findViewById(android.R.id.list);
174 lv.setTextFilterEnabled(true);
175 lv.setOnCreateContextMenuListener(this);
176 lv.setMultiChoiceModeListener(new ModeCallback());
178 mToken = MusicUtils.bindToService(this, osc);
181 gestureDetector = new GestureDetector(new NowPlayingGesture());
182 View nowplayingview = (View) findViewById(R.id.nowplaying);
184 swipetabs = new GestureDetector(new TabSwipe());
185 View mainview = (View) findViewById(android.R.id.list);
187 // Set the touch listener for the main view to be our custom gesture
189 mainview.setOnTouchListener(new View.OnTouchListener() {
190 public boolean onTouch(View v, MotionEvent event) {
191 if (swipetabs.onTouchEvent(event)) {
198 // Smaller now playing window buttons
199 mShare = (ImageButton) findViewById(R.id.share_song);
200 mPlay = (ImageButton) findViewById(R.id.media_play);
201 mNext = (ImageButton) findViewById(R.id.media_next);
202 mPrev = (ImageButton) findViewById(R.id.media_prev);
203 mMarket = (ImageButton) findViewById(R.id.market_music);
204 mDoSearch = (ImageButton) findViewById(R.id.doSearch);
205 mPlusPlaylist = (ImageButton) findViewById(R.id.plus_playlist);
206 mFlow = (ImageButton) findViewById(R.id.overFlow);
207 // I found that without setting the onClickListeners in Portrait mode
208 // only, flipping into Landscape force closes.
209 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
210 // Smaller now playing window actions
211 mDoSearch.setOnClickListener(mDoSearchListener);
212 mMarket.setOnClickListener(mMarketSearch);
213 mPlay.setOnClickListener(mMediaPlay);
214 mNext.setOnClickListener(mMediaNext);
215 mPrev.setOnClickListener(mMediaPrev);
216 mShare.setOnClickListener(mShareSong);
217 mFlow.setOnClickListener(mOverFlow);
218 // I only want this to show in the Playlist activity
219 mPlusPlaylist.setVisibility(View.GONE);
221 nowplayingview.setOnTouchListener(new View.OnTouchListener() {
222 public boolean onTouch(View vee, MotionEvent nowplayingevent) {
223 if (gestureDetector.onTouchEvent(nowplayingevent)) {
230 // ADW: Load the specified theme
231 String themePackage = MusicUtils.getThemePackageName(this,
232 MusicSettingsActivity.THEME_DEFAULT);
233 PackageManager pm = getPackageManager();
234 Resources themeResources = null;
235 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
237 themeResources = pm.getResourcesForApplication(themePackage);
238 } catch (NameNotFoundException e) {
239 // ADW The saved theme was uninstalled so we save the
241 MusicUtils.setThemePackageName(this,
242 MusicSettingsActivity.THEME_DEFAULT);
245 // Set Views for themes
246 if (themeResources != null) {
248 mAlbumTab = (LinearLayout) findViewById(R.id.album_tab);
249 mButtonBar = (TabWidget) findViewById(R.id.buttonbar);
250 mButtonBarArtist = (TextView) findViewById(R.id.artisttab);
251 mButtonBarAlbum = (TextView) findViewById(R.id.albumtab);
252 mButtonBarSong = (TextView) findViewById(R.id.songtab);
253 mButtonBarPlaylist = (TextView) findViewById(R.id.playlisttab);
254 mButtonBarNP = (TextView) findViewById(R.id.nowplayingtab);
255 ArtistAlbumBrowserActivity
256 .loadThemeResource(themeResources, themePackage,
257 "tab_album", mAlbumTab, THEME_ITEM_BACKGROUND);
258 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
259 themePackage, "buttonbar", mButtonBar,
260 THEME_ITEM_BACKGROUND);
261 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
262 themePackage, "tab_bg_artist", mButtonBarArtist,
263 THEME_ITEM_BACKGROUND);
264 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
265 themePackage, "tab_bg_album", mButtonBarAlbum,
266 THEME_ITEM_BACKGROUND);
267 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
268 themePackage, "tab_bg_song", mButtonBarSong,
269 THEME_ITEM_BACKGROUND);
270 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
271 themePackage, "tab_bg_playlist", mButtonBarPlaylist,
272 THEME_ITEM_BACKGROUND);
273 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
274 themePackage, "tab_bg_nowplaying", mButtonBarNP,
275 THEME_ITEM_BACKGROUND);
276 // Small now playing window
277 mNowPlaying = (LinearLayout) findViewById(R.id.nowplaying);
278 mInfoDivider = (ImageView) findViewById(R.id.info_divider);
279 mProgress = (ProgressBar) findViewById(R.id.progress);
280 mShare = (ImageButton) findViewById(R.id.share_song);
281 mPlay = (ImageButton) findViewById(R.id.media_play);
282 mNext = (ImageButton) findViewById(R.id.media_next);
283 mPrev = (ImageButton) findViewById(R.id.media_prev);
284 mMarket = (ImageButton) findViewById(R.id.market_music);
285 mDoSearch = (ImageButton) findViewById(R.id.doSearch);
286 mFlow = (ImageButton) findViewById(R.id.overFlow);
287 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
288 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
289 themePackage, "snp_background", mNowPlaying,
290 THEME_ITEM_BACKGROUND);
291 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
292 themePackage, "snp_progress", mProgress,
293 THEME_ITEM_BACKGROUND);
294 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
295 themePackage, "snp_info_divider", mInfoDivider,
296 THEME_ITEM_BACKGROUND);
297 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
298 themePackage, "snp_share", mShare,
299 THEME_ITEM_FOREGROUND);
300 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
301 themePackage, "snp_play", mPlay, THEME_ITEM_FOREGROUND);
302 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
303 themePackage, "snp_next", mNext, THEME_ITEM_FOREGROUND);
304 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
305 themePackage, "snp_prev", mPrev, THEME_ITEM_FOREGROUND);
306 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
307 themePackage, "snp_market", mMarket,
308 THEME_ITEM_FOREGROUND);
309 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
310 themePackage, "snp_search", mDoSearch,
311 THEME_ITEM_FOREGROUND);
312 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
313 themePackage, "snp_flow", mFlow, THEME_ITEM_FOREGROUND);
317 mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance();
318 if (mAdapter == null) {
319 // Log.i("@@@", "starting query");
320 mAdapter = new AlbumListAdapter(getApplication(), this,
321 R.layout.track_list_item, mAlbumCursor, new String[] {},
323 setListAdapter(mAdapter);
324 setTitle(R.string.working_albums);
325 getAlbumCursor(mAdapter.getQueryHandler(), null);
327 mAdapter.setActivity(this);
328 setListAdapter(mAdapter);
329 mAlbumCursor = mAdapter.getCursor();
330 if (mAlbumCursor != null) {
333 getAlbumCursor(mAdapter.getQueryHandler(), null);
338 private class ModeCallback implements ListView.MultiChoiceModeListener {
339 private View mMultiSelectActionBarView;
340 private TextView mSelectedConvCount;
341 private HashSet<Long> mSelectedThreadIds;
343 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
344 MenuInflater inflater = getMenuInflater();
345 mSelectedThreadIds = new HashSet<Long>();
346 inflater.inflate(R.menu.action_menu, menu);
348 if (mMultiSelectActionBarView == null) {
349 mMultiSelectActionBarView = (ViewGroup) LayoutInflater.from(
350 AlbumBrowserActivity.this).inflate(
351 R.layout.action_menu_layout, null);
353 mSelectedConvCount = (TextView) mMultiSelectActionBarView
354 .findViewById(R.id.selected_conv_count);
356 mode.setCustomView(mMultiSelectActionBarView);
357 ((TextView) mMultiSelectActionBarView.findViewById(R.id.title))
363 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
364 if (mMultiSelectActionBarView == null) {
366 ViewGroup v = (ViewGroup) LayoutInflater.from(
367 AlbumBrowserActivity.this).inflate(
368 R.layout.action_menu_layout, null);
369 mode.setCustomView(v);
371 mSelectedConvCount = (TextView) v
372 .findViewById(R.id.selected_conv_count);
378 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
379 int len = lv.getCount();
380 SparseBooleanArray checked = lv.getCheckedItemPositions();
381 switch (item.getItemId()) {
383 // This is used to do things in bulk.
384 for (int i = 0; i < len; i++)
385 if (checked.get(i)) {
391 // This is used to do things in bulk.
392 for (int i = 0; i < len; i++)
393 if (checked.get(i)) {
405 public void onDestroyActionMode(ActionMode mode) {
408 public void onItemCheckedStateChanged(ActionMode mode, int position,
409 long id, boolean checked) {
410 ListView listView = getListView();
411 final int checkedCount = listView.getCheckedItemCount();
412 mSelectedConvCount.setText(Integer.toString(checkedCount));
418 public Object onRetainNonConfigurationInstance() {
424 public void onSaveInstanceState(Bundle outcicle) {
425 // need to store the selected item so we don't lose it in case
426 // of an orientation switch. Otherwise we could lose it while
427 // in the middle of specifying a playlist to add the item to.
428 outcicle.putString("selectedalbum", mCurrentAlbumId);
429 outcicle.putString("artist", mArtistId);
430 super.onSaveInstanceState(outcicle);
434 class TabSwipe extends SimpleOnGestureListener {
436 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
438 Intent intent = new Intent(Intent.ACTION_PICK);
441 if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
443 if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
444 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
445 intent.setDataAndType(Uri.EMPTY,
446 "vnd.android.cursor.dir/track");
447 intent.putExtra("withtabs", true);
448 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
449 startActivity(intent);
451 AlbumBrowserActivity.this.overridePendingTransition(
452 R.anim.slide_in_right, R.anim.slide_out_left);
454 } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
455 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
456 intent.setDataAndType(Uri.EMPTY,
457 "vnd.android.cursor.dir/artistalbum");
458 intent.putExtra("withtabs", true);
459 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
460 startActivity(intent);
462 AlbumBrowserActivity.this.overridePendingTransition(
463 R.anim.slide_in_left, R.anim.slide_out_right);
468 } catch (Exception e) {
469 Log.e("Fling", "There was an error processing the Fling event:"
475 // It is necessary to return true from onDown for the onFling event to
478 public boolean onDown(MotionEvent e) {
484 class NowPlayingGesture extends SimpleOnGestureListener {
486 public boolean onFling(MotionEvent e3, MotionEvent e4, float vX,
488 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
490 if (np_swipe_gesture_db.equals("0")) {
491 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
492 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
497 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
499 if (np_swipe_gesture_db.equals("1")) {
500 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
501 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
506 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
508 if (np_swipe_gesture_db.equals("2")) {
509 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
510 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
514 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
516 if (np_swipe_gesture_db.equals("3")) {
517 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
518 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
527 public boolean onDown(MotionEvent f) {
532 // Smaller now playing window OnClickListerners
533 private View.OnClickListener mMarketSearch = new View.OnClickListener() {
534 public void onClick(View v) {
535 Uri marketUri = null;
537 marketUri = Uri.parse("market://search?q= "
538 + mService.getArtistName());
539 } catch (RemoteException e) {
540 // TODO Auto-generated catch block
543 Intent marketIntent = new Intent(Intent.ACTION_VIEW)
545 startActivity(marketIntent);
550 private View.OnClickListener mShareSong = new View.OnClickListener() {
553 public void onClick(View v) {
554 // TODO Auto-generated method stub
557 } catch (RemoteException e) {
558 // TODO Auto-generated catch block
566 private String shareCurrentTrack() throws RemoteException {
567 if (mService.getTrackName() == null || mService.getArtistName() == null) {
571 Intent shareIntent = new Intent();
572 String currentTrackMessage = "Now listening to: "
573 + mService.getTrackName() + " by " + mService.getArtistName();
575 shareIntent.setAction(Intent.ACTION_SEND);
576 shareIntent.setType("text/plain");
577 shareIntent.putExtra(Intent.EXTRA_TEXT, currentTrackMessage);
579 startActivity(Intent.createChooser(shareIntent, "Share track using"));
580 return currentTrackMessage;
583 private View.OnClickListener mOverFlow = new View.OnClickListener() {
584 public void onClick(View v) {
588 private View.OnClickListener mDoSearchListener = new View.OnClickListener() {
589 public void onClick(View v) {
593 private View.OnClickListener mMediaPlay = new View.OnClickListener() {
594 public void onClick(View v) {
598 private View.OnClickListener mMediaNext = new View.OnClickListener() {
599 public void onClick(View v) {
603 private View.OnClickListener mMediaPrev = new View.OnClickListener() {
604 public void onClick(View v) {
610 public void onDestroy() {
611 ListView lv = getListView();
613 mLastListPosCourse = lv.getFirstVisiblePosition();
614 View cv = lv.getChildAt(0);
616 mLastListPosFine = cv.getTop();
619 MusicUtils.unbindFromService(mToken);
620 // If we have an adapter and didn't send it off to another activity yet,
622 // close its cursor, which we do by assigning a null cursor to it. Doing
624 // instead of closing the cursor directly keeps the framework from
626 // the closed cursor later.
627 if (!mAdapterSent && mAdapter != null) {
628 mAdapter.changeCursor(null);
630 // Because we pass the adapter to the next activity, we need to make
631 // sure it doesn't keep a reference to this activity. We can do this
632 // by clearing its DatasetObservers, which setListAdapter(null) does.
633 setListAdapter(null);
635 unregisterReceiver(mScanListener);
639 private void startPlayback() {
641 if (mService == null)
643 Intent intent = getIntent();
644 String filename = "";
645 Uri uri = intent.getData();
646 if (uri != null && uri.toString().length() > 0) {
647 // If this is a file:// URI, just use the path directly instead
648 // of going through the open-from-filedescriptor codepath.
649 String scheme = uri.getScheme();
650 if ("file".equals(scheme)) {
651 filename = uri.getPath();
653 filename = uri.toString();
658 mService.openFile(filename);
660 setIntent(new Intent());
661 } catch (Exception ex) {
662 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
667 private ServiceConnection osc = new ServiceConnection() {
668 public void onServiceConnected(ComponentName classname, IBinder obj) {
669 mService = IMediaPlaybackService.Stub.asInterface(obj);
671 // This is for orientation changes or entering the tab from a
672 // different activity
673 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
674 setPauseButtonImage();
675 // This is so the controls will load correctly according to the
677 MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
680 if (mService.getAudioId() >= 0 || mService.isPlaying()
681 || mService.getPath() != null) {
684 } catch (RemoteException ex) {
686 if (getIntent().getData() == null) {
687 Intent intent = new Intent(Intent.ACTION_MAIN);
688 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
689 intent.setClass(AlbumBrowserActivity.this,
690 MusicBrowserActivity.class);
691 startActivity(intent);
695 public void onServiceDisconnected(ComponentName classname) {
701 public void onResume() {
703 IntentFilter f = new IntentFilter();
704 f.addAction(MediaPlaybackService.META_CHANGED);
705 f.addAction(MediaPlaybackService.QUEUE_CHANGED);
706 f.addAction(MediaPlaybackService.PROGRESSBAR_CHANGED);
707 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
708 registerReceiver(mTrackListListener, f);
710 MusicUtils.setSpinnerState(this);
713 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
715 public void onReceive(Context context, Intent intent) {
716 String action = intent.getAction();
717 getListView().invalidateViews();
718 MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
719 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
721 if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
722 setPauseButtonImage();
727 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
729 public void onReceive(Context context, Intent intent) {
730 MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
731 mReScanHandler.sendEmptyMessage(0);
732 if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
733 MusicUtils.clearAlbumArtCache();
738 private Handler mReScanHandler = new Handler() {
740 public void handleMessage(Message msg) {
741 if (mAdapter != null) {
742 getAlbumCursor(mAdapter.getQueryHandler(), null);
748 public void onPause() {
749 unregisterReceiver(mTrackListListener);
750 mReScanHandler.removeCallbacksAndMessages(null);
754 public void init(Cursor c) {
756 if (mAdapter == null) {
759 mAdapter.changeCursor(c); // also sets mAlbumCursor
761 if (mAlbumCursor == null) {
762 MusicUtils.displayDatabaseError(this);
764 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
768 // restore previous position
769 if (mLastListPosCourse >= 0) {
770 getListView().setSelectionFromTop(mLastListPosCourse,
772 mLastListPosCourse = -1;
775 MusicUtils.hideDatabaseError(this);
776 MusicUtils.updateButtonBar(this, R.id.albumtab);
780 private void setTitle() {
781 CharSequence fancyName = "";
782 if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
783 mAlbumCursor.moveToFirst();
784 fancyName = mAlbumCursor.getString(mAlbumCursor
785 .getColumnIndex(MediaStore.Audio.Albums.ARTIST));
786 if (fancyName == null
787 || fancyName.equals(MediaStore.UNKNOWN_STRING))
788 fancyName = getText(R.string.unknown_artist_name);
791 if (mArtistId != null && fancyName != null)
794 setTitle(R.string.albums_title);
798 public void onCreateContextMenu(ContextMenu menu, View view,
799 ContextMenuInfo menuInfoIn) {
800 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
801 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
802 R.string.add_to_playlist);
803 MusicUtils.makePlaylistMenu(this, sub);
804 menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
806 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
807 mAlbumCursor.moveToPosition(mi.position);
808 mCurrentAlbumId = mAlbumCursor.getString(mAlbumCursor
809 .getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
810 mCurrentAlbumName = mAlbumCursor.getString(mAlbumCursor
811 .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
812 mCurrentArtistNameForAlbum = mAlbumCursor.getString(mAlbumCursor
813 .getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
814 mIsUnknownArtist = mCurrentArtistNameForAlbum == null
815 || mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
816 mIsUnknownAlbum = mCurrentAlbumName == null
817 || mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
818 if (mIsUnknownAlbum) {
819 menu.setHeaderTitle(getString(R.string.unknown_album_name));
821 menu.setHeaderTitle(mCurrentAlbumName);
823 if (!mIsUnknownAlbum || !mIsUnknownArtist) {
824 menu.add(0, SEARCH, 0, R.string.search_title);
829 public boolean onContextItemSelected(MenuItem item) {
830 switch (item.getItemId()) {
831 case PLAY_SELECTION: {
832 // play the selected album
833 long[] list = MusicUtils.getSongListForAlbum(this,
834 Long.parseLong(mCurrentAlbumId));
835 MusicUtils.playAll(this, list, 0);
840 long[] list = MusicUtils.getSongListForAlbum(this,
841 Long.parseLong(mCurrentAlbumId));
842 MusicUtils.addToCurrentPlaylist(this, list);
847 Intent intent = new Intent();
848 intent.setClass(this, CreatePlaylist.class);
849 startActivityForResult(intent, NEW_PLAYLIST);
853 case PLAYLIST_SELECTED: {
854 long[] list = MusicUtils.getSongListForAlbum(this,
855 Long.parseLong(mCurrentAlbumId));
856 long playlist = item.getIntent().getLongExtra("playlist", 0);
857 MusicUtils.addToPlaylist(this, list, playlist);
861 long[] list = MusicUtils.getSongListForAlbum(this,
862 Long.parseLong(mCurrentAlbumId));
864 if (android.os.Environment.isExternalStorageRemovable()) {
865 f = getString(R.string.delete_album_desc);
867 f = getString(R.string.delete_album_desc_nosdcard);
869 String desc = String.format(f, mCurrentAlbumName);
870 Bundle b = new Bundle();
871 b.putString("description", desc);
872 b.putLongArray("items", list);
873 Intent intent = new Intent();
874 intent.setClass(this, DeleteItems.class);
876 startActivityForResult(intent, -1);
884 return super.onContextItemSelected(item);
888 CharSequence title = null;
891 Intent i = new Intent();
892 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
893 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
896 if (!mIsUnknownAlbum) {
897 query = mCurrentAlbumName;
898 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
899 title = mCurrentAlbumName;
901 if (!mIsUnknownArtist) {
902 query = query + " " + mCurrentArtistNameForAlbum;
903 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST,
904 mCurrentArtistNameForAlbum);
905 title = title + " " + mCurrentArtistNameForAlbum;
907 // Since we hide the 'search' menu item when both album and artist are
908 // unknown, the query and title strings will have at least one of those.
909 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS,
910 MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
911 title = getString(R.string.mediasearch, title);
912 i.putExtra(SearchManager.QUERY, query);
914 startActivity(Intent.createChooser(i, title));
918 protected void onActivityResult(int requestCode, int resultCode,
920 switch (requestCode) {
922 if (resultCode == RESULT_CANCELED) {
925 getAlbumCursor(mAdapter.getQueryHandler(), null);
930 if (resultCode == RESULT_OK) {
931 Uri uri = intent.getData();
933 long[] list = MusicUtils.getSongListForAlbum(this,
934 Long.parseLong(mCurrentAlbumId));
935 MusicUtils.addToPlaylist(this, list,
936 Long.parseLong(uri.getLastPathSegment()));
944 protected void onListItemClick(ListView l, View v, int position, long id) {
945 Intent intent = new Intent(Intent.ACTION_PICK);
946 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
947 intent.putExtra("album", Long.valueOf(id).toString());
948 intent.putExtra("artist", mArtistId);
949 startActivity(intent);
953 public boolean onCreateOptionsMenu(Menu menu) {
954 super.onCreateOptionsMenu(menu);
955 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle);
956 menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all);
957 menu.add(0, SETTINGS, 0, R.string.settings);
962 public boolean onPrepareOptionsMenu(Menu menu) {
963 MusicUtils.setPartyShuffleMenuIcon(menu);
964 return super.onPrepareOptionsMenu(menu);
968 public boolean onOptionsItemSelected(MenuItem item) {
971 switch (item.getItemId()) {
973 MusicUtils.togglePartyShuffle();
977 cursor = MusicUtils.query(this,
978 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
979 new String[] { MediaStore.Audio.Media._ID },
980 MediaStore.Audio.Media.IS_MUSIC + "=1", null,
981 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
982 if (cursor != null) {
983 MusicUtils.shuffleAll(this, cursor);
988 intent = new Intent();
989 intent.setClass(this, MusicSettingsActivity.class);
990 startActivityForResult(intent, SETTINGS);
993 return super.onOptionsItemSelected(item);
996 private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) {
997 String[] cols = new String[] { MediaStore.Audio.Albums._ID,
998 MediaStore.Audio.Albums.ARTIST, MediaStore.Audio.Albums.ALBUM,
999 MediaStore.Audio.Albums.ALBUM_ART };
1002 if (mArtistId != null) {
1003 Uri uri = MediaStore.Audio.Artists.Albums.getContentUri("external",
1004 Long.valueOf(mArtistId));
1005 if (!TextUtils.isEmpty(filter)) {
1006 uri = uri.buildUpon()
1007 .appendQueryParameter("filter", Uri.encode(filter))
1010 if (async != null) {
1011 async.startQuery(0, null, uri, cols, null, null,
1012 MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
1014 ret = MusicUtils.query(this, uri, cols, null, null,
1015 MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
1018 Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
1019 if (!TextUtils.isEmpty(filter)) {
1020 uri = uri.buildUpon()
1021 .appendQueryParameter("filter", Uri.encode(filter))
1024 if (async != null) {
1025 async.startQuery(0, null, uri, cols, null, null,
1026 MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
1028 ret = MusicUtils.query(this, uri, cols, null, null,
1029 MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
1035 static class AlbumListAdapter extends SimpleCursorAdapter implements
1038 private final BitmapDrawable mDefaultAlbumIcon;
1039 private int mAlbumIdx;
1040 private int mArtistIdx;
1041 private int mAlbumArtIndex;
1042 private final Resources mResources;
1043 private final StringBuilder mStringBuilder = new StringBuilder();
1044 private String mUnknownAlbum;
1045 private final String mUnknownArtist;
1046 private final String mAlbumSongSeparator;
1047 private final Object[] mFormatArgs = new Object[1];
1048 private AlphabetIndexer mIndexer;
1049 private AlbumBrowserActivity mActivity;
1050 private AsyncQueryHandler mQueryHandler;
1051 private String mConstraint = null;
1052 private boolean mConstraintIsValid = false;
1054 static class ViewHolder {
1057 ImageView play_indicator;
1060 public FrameLayout mContextMenu;
1063 class QueryHandler extends AsyncQueryHandler {
1064 QueryHandler(ContentResolver res) {
1069 protected void onQueryComplete(int token, Object cookie,
1071 // Log.i("@@@", "query complete");
1072 mActivity.init(cursor);
1076 AlbumListAdapter(Context context, AlbumBrowserActivity currentactivity,
1077 int layout, Cursor cursor, String[] from, int[] to) {
1078 super(context, layout, cursor, from, to);
1080 mActivity = currentactivity;
1081 mQueryHandler = new QueryHandler(context.getContentResolver());
1083 mUnknownAlbum = context.getString(R.string.unknown_album_name);
1084 mUnknownArtist = context.getString(R.string.unknown_artist_name);
1085 mAlbumSongSeparator = context
1086 .getString(R.string.albumsongseparator);
1088 Resources r = context.getResources();
1090 Bitmap b = BitmapFactory.decodeResource(r,
1091 R.drawable.albumart_mp_unknown_list);
1092 mDefaultAlbumIcon = new BitmapDrawable(context.getResources(), b);
1093 // no filter or dither, it's a lot faster and we can't tell the
1095 mDefaultAlbumIcon.setFilterBitmap(false);
1096 mDefaultAlbumIcon.setDither(false);
1097 getColumnIndices(cursor);
1098 mResources = context.getResources();
1101 private void getColumnIndices(Cursor cursor) {
1102 if (cursor != null) {
1103 mAlbumIdx = cursor.getColumnIndexOrThrow(AlbumColumns.ALBUM);
1104 mArtistIdx = cursor.getColumnIndexOrThrow(AlbumColumns.ARTIST);
1105 mAlbumArtIndex = cursor
1106 .getColumnIndexOrThrow(AlbumColumns.ALBUM_ART);
1108 if (mIndexer != null) {
1109 mIndexer.setCursor(cursor);
1111 mIndexer = new MusicAlphabetIndexer(cursor, mAlbumIdx,
1112 mResources.getString(R.string.fast_scroll_alphabet));
1117 public void setActivity(AlbumBrowserActivity newactivity) {
1118 mActivity = newactivity;
1121 public AsyncQueryHandler getQueryHandler() {
1122 return mQueryHandler;
1125 private View.OnClickListener mCML = new View.OnClickListener() {
1126 public void onClick(View v) {
1127 v.showContextMenu();
1133 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1134 View v = super.newView(context, cursor, parent);
1135 ViewHolder vh = new ViewHolder();
1136 vh.line1 = (TextView) v.findViewById(R.id.line1);
1137 vh.line2 = (TextView) v.findViewById(R.id.line2);
1138 vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
1139 vh.icon = (ImageView) v.findViewById(R.id.icon);
1140 vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
1141 vh.icon.setPadding(0, 0, 1, 0);
1143 vh.mCM = (ImageView) v.findViewById(R.id.CM);
1144 vh.mContextMenu = (FrameLayout) v
1145 .findViewById(R.id.second_column_icon);
1146 vh.mContextMenu.setOnClickListener(mCML);
1147 // ADW: Load the specified theme
1148 String themePackage = MusicUtils.getThemePackageName(context,
1149 MusicSettingsActivity.THEME_DEFAULT);
1150 PackageManager pm = context.getPackageManager();
1151 Resources themeResources = null;
1152 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
1155 .getResourcesForApplication(themePackage);
1156 } catch (NameNotFoundException e) {
1157 // ADW The saved theme was uninstalled so we save the
1159 MusicUtils.setThemePackageName(context,
1160 MusicSettingsActivity.THEME_DEFAULT);
1163 if (themeResources != null) {
1164 int line1 = themeResources.getIdentifier(
1165 "album_tab_artist_name_color", "color", themePackage);
1167 vh.line1.setTextColor(themeResources.getColor(line1));
1169 int line2 = themeResources.getIdentifier(
1170 "album_tab_album_name_color", "color", themePackage);
1172 vh.line2.setTextColor(themeResources.getColor(line2));
1174 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
1175 themePackage, "bt_context_menu", vh.mCM,
1176 THEME_ITEM_FOREGROUND);
1182 public void bindView(View view, Context context, Cursor cursor) {
1184 // ADW: Load the specified theme
1185 String themePackage = MusicUtils.getThemePackageName(context,
1186 MusicSettingsActivity.THEME_DEFAULT);
1187 PackageManager pm = context.getPackageManager();
1188 Resources themeResources = null;
1189 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
1192 .getResourcesForApplication(themePackage);
1193 } catch (NameNotFoundException e) {
1194 // ADW The saved theme was uninstalled so we save the
1196 MusicUtils.setThemePackageName(context,
1197 MusicSettingsActivity.THEME_DEFAULT);
1201 ViewHolder vh = (ViewHolder) view.getTag();
1203 String name = cursor.getString(mAlbumIdx);
1204 String displayname = name;
1205 boolean unknown = name == null
1206 || name.equals(MediaStore.UNKNOWN_STRING);
1208 displayname = mUnknownAlbum;
1210 vh.line1.setText(displayname);
1211 name = cursor.getString(mArtistIdx);
1213 if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
1214 displayname = mUnknownAlbum;
1216 vh.line2.setText(displayname);
1217 ImageView iv = vh.icon;
1218 // We don't actually need the path to the thumbnail file,
1219 // we just use it to see if there is album art or not
1220 String art = cursor.getString(mAlbumArtIndex);
1221 long aid = cursor.getLong(0);
1222 if (unknown || art == null || art.length() == 0) {
1223 iv.setImageDrawable(null);
1225 Drawable d = MusicUtils.getCachedArtwork(context, aid,
1227 iv.setImageDrawable(d);
1230 long currentalbumid = MusicUtils.getCurrentAlbumId();
1231 iv = vh.play_indicator;
1232 if (currentalbumid == aid) {
1233 iv.setBackgroundResource(R.anim.peak_meter);
1234 AnimationDrawable frameAnimation = (AnimationDrawable) iv
1236 if (themeResources != null) {
1237 int peak = themeResources.getIdentifier("peak_meter",
1238 "anim", themePackage);
1240 iv.setBackgroundDrawable(themeResources
1241 .getDrawable(peak));
1244 // Start the animation (looped playback by default).
1245 frameAnimation.start();
1246 iv.setVisibility(View.VISIBLE);
1248 iv.setVisibility(View.GONE);
1253 public void changeCursor(Cursor cursor) {
1254 if (mActivity.isFinishing() && cursor != null) {
1258 if (cursor != mActivity.mAlbumCursor) {
1259 mActivity.mAlbumCursor = cursor;
1260 getColumnIndices(cursor);
1261 super.changeCursor(cursor);
1266 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1267 String s = constraint.toString();
1268 if (mConstraintIsValid
1269 && ((s == null && mConstraint == null) || (s != null && s
1270 .equals(mConstraint)))) {
1273 Cursor c = mActivity.getAlbumCursor(null, s);
1275 mConstraintIsValid = true;
1279 public Object[] getSections() {
1280 return mIndexer.getSections();
1283 public int getPositionForSection(int section) {
1284 return mIndexer.getPositionForSection(section);
1287 public int getSectionForPosition(int position) {
1292 private Cursor mAlbumCursor;
1293 private String mArtistId;
1295 public void onServiceConnected(ComponentName name, IBinder service) {
1296 MusicUtils.updateNowPlaying(this);
1299 public void onServiceDisconnected(ComponentName name) {
1303 // Methods for media control
1304 private void doPauseResume() {
1306 if (mService != null) {
1307 if (mService.isPlaying()) {
1312 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
1313 setPauseButtonImage();
1316 } catch (RemoteException ex) {
1320 private void setPauseButtonImage() {
1321 // ADW: Load the specified theme
1322 String themePackage = MusicUtils.getThemePackageName(this,
1323 MusicSettingsActivity.THEME_DEFAULT);
1324 PackageManager pm = getPackageManager();
1325 Resources themeResources = null;
1326 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
1328 themeResources = pm.getResourcesForApplication(themePackage);
1329 } catch (NameNotFoundException e) {
1330 // ADW The saved theme was uninstalled so we save the
1332 MusicUtils.setThemePackageName(this,
1333 MusicSettingsActivity.THEME_DEFAULT);
1337 if (mService != null && mService.isPlaying()) {
1338 mPlay.setImageResource(R.drawable.ic_media_pause);
1339 if (themeResources != null) {
1340 ArtistAlbumBrowserActivity.loadThemeResource(
1341 themeResources, themePackage, "snp_pause", mPlay,
1342 THEME_ITEM_FOREGROUND);
1345 mPlay.setImageResource(R.drawable.ic_appwidget_music_play);
1346 if (themeResources != null) {
1347 ArtistAlbumBrowserActivity.loadThemeResource(
1348 themeResources, themePackage, "snp_play", mPlay,
1349 THEME_ITEM_FOREGROUND);
1352 } catch (RemoteException ex) {
1356 private void refreshProgress() {
1357 ProgressBar mProgress = (ProgressBar) findViewById(R.id.progress);
1358 mProgress.setMax(1000);
1360 if ((MusicUtils.sService.position() >= 0)
1361 && (MusicUtils.sService.duration() > 0)) {
1362 mProgress.setProgress((int) (1000 * MusicUtils.sService
1363 .position() / MusicUtils.sService.duration()));
1365 mProgress.setProgress(1000);
1367 } catch (Exception e) {
1368 // TODO Auto-generated catch block
1369 e.printStackTrace();
1374 private void doPrev() {
1375 if (mService == null)
1378 if (mService.position() < 2000) {
1384 setPauseButtonImage();
1385 } catch (RemoteException ex) {
1389 private void doNext() {
1390 if (mService == null)
1394 setPauseButtonImage();
1395 } catch (RemoteException ex) {
1399 private void playRecentlyAdded() {
1400 // do a query for all songs added in the last X weeks
1401 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
1402 final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
1403 String where = MediaStore.MediaColumns.DATE_ADDED + ">"
1404 + (System.currentTimeMillis() / 1000 - X);
1405 Cursor cursor = MusicUtils.query(this,
1406 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
1407 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
1409 if (cursor == null) {
1410 // Todo: show a message
1414 int len = cursor.getCount();
1415 long[] list = new long[len];
1416 for (int i = 0; i < len; i++) {
1417 cursor.moveToNext();
1418 list[i] = cursor.getLong(0);
1420 MusicUtils.playAll(this, list, 0);
1421 } catch (SQLiteException ex) {
1428 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
1429 if (keyCode == KeyEvent.KEYCODE_BACK) {
1430 back_button_db = mPreferences.getString("back_button_db", "0");
1431 if (back_button_db.equals("0")) {
1432 MusicUtils.togglePartyShuffle();
1435 if (keyCode == KeyEvent.KEYCODE_BACK) {
1436 back_button_db = mPreferences.getString("back_button_db", "1");
1437 if (back_button_db.equals("1")) {
1438 playRecentlyAdded();
1443 if (keyCode == KeyEvent.KEYCODE_BACK) {
1444 back_button_db = mPreferences.getString("back_button_db", "2");
1445 if (back_button_db.equals("2")) {
1447 cursor = MusicUtils.query(this,
1448 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1449 new String[] { BaseColumns._ID },
1450 AudioColumns.IS_MUSIC + "=1", null,
1451 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
1452 if (cursor != null) {
1453 MusicUtils.shuffleAll(this, cursor);
1460 return super.onKeyLongPress(keyCode, event);