You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1371 lines
51 KiB
1371 lines
51 KiB
/*
|
|
* 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.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.KeyguardManager;
|
|
import android.app.SearchManager;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentName;
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentUris;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.content.res.Configuration;
|
|
import android.database.Cursor;
|
|
import android.graphics.Bitmap;
|
|
import android.media.audiofx.AudioEffect;
|
|
import android.media.AudioManager;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import android.provider.MediaStore;
|
|
import android.text.Layout;
|
|
import android.text.TextUtils.TruncateAt;
|
|
import android.util.Log;
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.MotionEvent;
|
|
import android.view.SubMenu;
|
|
import android.view.View;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.Window;
|
|
import android.widget.ImageButton;
|
|
import android.widget.ImageView;
|
|
import android.widget.ProgressBar;
|
|
import android.widget.SeekBar;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
|
|
public class MediaPlaybackActivity extends Activity
|
|
implements MusicUtils.Defs, View.OnTouchListener, View.OnLongClickListener {
|
|
private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
|
|
|
|
private boolean mSeeking = false;
|
|
private boolean mDeviceHasDpad;
|
|
private long mStartSeekPos = 0;
|
|
private long mLastSeekEventTime;
|
|
private IMediaPlaybackService mService = null;
|
|
private RepeatingImageButton mPrevButton;
|
|
private ImageButton mPauseButton;
|
|
private RepeatingImageButton mNextButton;
|
|
private ImageButton mRepeatButton;
|
|
private ImageButton mShuffleButton;
|
|
private ImageButton mQueueButton;
|
|
private Worker mAlbumArtWorker;
|
|
private AlbumArtHandler mAlbumArtHandler;
|
|
private Toast mToast;
|
|
private int mTouchSlop;
|
|
private ServiceToken mToken;
|
|
|
|
public MediaPlaybackActivity() {}
|
|
|
|
/** Called when the activity is first created. */
|
|
@Override
|
|
public void onCreate(Bundle icicle) {
|
|
super.onCreate(icicle);
|
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
|
|
|
mAlbumArtWorker = new Worker("album art worker");
|
|
mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
|
|
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
setContentView(R.layout.audio_player);
|
|
|
|
mCurrentTime = (TextView) findViewById(R.id.currenttime);
|
|
mTotalTime = (TextView) findViewById(R.id.totaltime);
|
|
mProgress = (ProgressBar) findViewById(android.R.id.progress);
|
|
mAlbum = (ImageView) findViewById(R.id.album);
|
|
mArtistName = (TextView) findViewById(R.id.artistname);
|
|
mAlbumName = (TextView) findViewById(R.id.albumname);
|
|
mTrackName = (TextView) findViewById(R.id.trackname);
|
|
|
|
View v = (View) mArtistName.getParent();
|
|
v.setOnTouchListener(this);
|
|
v.setOnLongClickListener(this);
|
|
|
|
v = (View) mAlbumName.getParent();
|
|
v.setOnTouchListener(this);
|
|
v.setOnLongClickListener(this);
|
|
|
|
v = (View) mTrackName.getParent();
|
|
v.setOnTouchListener(this);
|
|
v.setOnLongClickListener(this);
|
|
|
|
mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
|
|
mPrevButton.setOnClickListener(mPrevListener);
|
|
mPrevButton.setRepeatListener(mRewListener, 260);
|
|
mPauseButton = (ImageButton) findViewById(R.id.pause);
|
|
mPauseButton.requestFocus();
|
|
mPauseButton.setOnClickListener(mPauseListener);
|
|
mNextButton = (RepeatingImageButton) findViewById(R.id.next);
|
|
mNextButton.setOnClickListener(mNextListener);
|
|
mNextButton.setRepeatListener(mFfwdListener, 260);
|
|
seekmethod = 1;
|
|
|
|
mDeviceHasDpad =
|
|
(getResources().getConfiguration().navigation == Configuration.NAVIGATION_DPAD);
|
|
|
|
mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
|
|
mQueueButton.setOnClickListener(mQueueListener);
|
|
mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
|
|
mShuffleButton.setOnClickListener(mShuffleListener);
|
|
mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
|
|
mRepeatButton.setOnClickListener(mRepeatListener);
|
|
|
|
if (mProgress instanceof SeekBar) {
|
|
SeekBar seeker = (SeekBar) mProgress;
|
|
seeker.setOnSeekBarChangeListener(mSeekListener);
|
|
}
|
|
mProgress.setMax(1000);
|
|
|
|
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
|
|
}
|
|
|
|
int mInitialX = -1;
|
|
int mLastX = -1;
|
|
int mTextWidth = 0;
|
|
int mViewWidth = 0;
|
|
boolean mDraggingLabel = false;
|
|
|
|
TextView textViewForContainer(View v) {
|
|
View vv = v.findViewById(R.id.artistname);
|
|
if (vv != null) return (TextView) vv;
|
|
vv = v.findViewById(R.id.albumname);
|
|
if (vv != null) return (TextView) vv;
|
|
vv = v.findViewById(R.id.trackname);
|
|
if (vv != null) return (TextView) vv;
|
|
return null;
|
|
}
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
int action = event.getAction();
|
|
TextView tv = textViewForContainer(v);
|
|
if (tv == null) {
|
|
return false;
|
|
}
|
|
if (action == MotionEvent.ACTION_DOWN) {
|
|
v.setBackgroundColor(0xff606060);
|
|
mInitialX = mLastX = (int) event.getX();
|
|
mDraggingLabel = false;
|
|
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
v.setBackgroundColor(0);
|
|
if (mDraggingLabel) {
|
|
Message msg = mLabelScroller.obtainMessage(0, tv);
|
|
mLabelScroller.sendMessageDelayed(msg, 1000);
|
|
}
|
|
} else if (action == MotionEvent.ACTION_MOVE) {
|
|
if (mDraggingLabel) {
|
|
int scrollx = tv.getScrollX();
|
|
int x = (int) event.getX();
|
|
int delta = mLastX - x;
|
|
if (delta != 0) {
|
|
mLastX = x;
|
|
scrollx += delta;
|
|
if (scrollx > mTextWidth) {
|
|
// scrolled the text completely off the view to the left
|
|
scrollx -= mTextWidth;
|
|
scrollx -= mViewWidth;
|
|
}
|
|
if (scrollx < -mViewWidth) {
|
|
// scrolled the text completely off the view to the right
|
|
scrollx += mViewWidth;
|
|
scrollx += mTextWidth;
|
|
}
|
|
tv.scrollTo(scrollx, 0);
|
|
}
|
|
return true;
|
|
}
|
|
int delta = mInitialX - (int) event.getX();
|
|
if (Math.abs(delta) > mTouchSlop) {
|
|
// start moving
|
|
mLabelScroller.removeMessages(0, tv);
|
|
|
|
// Only turn ellipsizing off when it's not already off, because it
|
|
// causes the scroll position to be reset to 0.
|
|
if (tv.getEllipsize() != null) {
|
|
tv.setEllipsize(null);
|
|
}
|
|
Layout ll = tv.getLayout();
|
|
// layout might be null if the text just changed, or ellipsizing
|
|
// was just turned off
|
|
if (ll == null) {
|
|
return false;
|
|
}
|
|
// get the non-ellipsized line width, to determine whether scrolling
|
|
// should even be allowed
|
|
mTextWidth = (int) tv.getLayout().getLineWidth(0);
|
|
mViewWidth = tv.getWidth();
|
|
if (mViewWidth > mTextWidth) {
|
|
tv.setEllipsize(TruncateAt.END);
|
|
v.cancelLongPress();
|
|
return false;
|
|
}
|
|
mDraggingLabel = true;
|
|
tv.setHorizontalFadingEdgeEnabled(true);
|
|
v.cancelLongPress();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Handler mLabelScroller = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
TextView tv = (TextView) msg.obj;
|
|
int x = tv.getScrollX();
|
|
x = x * 3 / 4;
|
|
tv.scrollTo(x, 0);
|
|
if (x == 0) {
|
|
tv.setEllipsize(TruncateAt.END);
|
|
} else {
|
|
Message newmsg = obtainMessage(0, tv);
|
|
mLabelScroller.sendMessageDelayed(newmsg, 15);
|
|
}
|
|
}
|
|
};
|
|
|
|
public boolean onLongClick(View view) {
|
|
CharSequence title = null;
|
|
String mime = null;
|
|
String query = null;
|
|
String artist;
|
|
String album;
|
|
String song;
|
|
long audioid;
|
|
|
|
try {
|
|
artist = mService.getArtistName();
|
|
album = mService.getAlbumName();
|
|
song = mService.getTrackName();
|
|
audioid = mService.getAudioId();
|
|
} catch (RemoteException ex) {
|
|
return true;
|
|
} catch (NullPointerException ex) {
|
|
// we might not actually have the service yet
|
|
return true;
|
|
}
|
|
|
|
if (MediaStore.UNKNOWN_STRING.equals(album) && MediaStore.UNKNOWN_STRING.equals(artist)
|
|
&& song != null && song.startsWith("recording")) {
|
|
// not music
|
|
return false;
|
|
}
|
|
|
|
if (audioid < 0) {
|
|
return false;
|
|
}
|
|
|
|
Cursor c = MusicUtils.query(this,
|
|
ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioid),
|
|
new String[] {MediaStore.Audio.Media.IS_MUSIC}, null, null, null);
|
|
boolean ismusic = true;
|
|
if (c != null) {
|
|
if (c.moveToFirst()) {
|
|
ismusic = c.getInt(0) != 0;
|
|
}
|
|
c.close();
|
|
}
|
|
if (!ismusic) {
|
|
return false;
|
|
}
|
|
|
|
boolean knownartist = (artist != null) && !MediaStore.UNKNOWN_STRING.equals(artist);
|
|
|
|
boolean knownalbum = (album != null) && !MediaStore.UNKNOWN_STRING.equals(album);
|
|
|
|
if (knownartist && view.equals(mArtistName.getParent())) {
|
|
title = artist;
|
|
query = artist;
|
|
mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
|
|
} else if (knownalbum && view.equals(mAlbumName.getParent())) {
|
|
title = album;
|
|
if (knownartist) {
|
|
query = artist + " " + album;
|
|
} else {
|
|
query = album;
|
|
}
|
|
mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
|
|
} else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
|
|
if ((song == null) || MediaStore.UNKNOWN_STRING.equals(song)) {
|
|
// A popup of the form "Search for null/'' using ..." is pretty
|
|
// unhelpful, plus, we won't find any way to buy it anyway.
|
|
return true;
|
|
}
|
|
|
|
title = song;
|
|
if (knownartist) {
|
|
query = artist + " " + song;
|
|
} else {
|
|
query = song;
|
|
}
|
|
mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
|
|
} else {
|
|
throw new RuntimeException("shouldn't be here");
|
|
}
|
|
title = getString(R.string.mediasearch, title);
|
|
|
|
Intent i = new Intent();
|
|
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
|
|
i.putExtra(SearchManager.QUERY, query);
|
|
if (knownartist) {
|
|
i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
|
|
}
|
|
if (knownalbum) {
|
|
i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
|
|
}
|
|
i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
|
|
i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
|
|
|
|
startActivity(Intent.createChooser(i, title));
|
|
return true;
|
|
}
|
|
|
|
private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
|
|
public void onStartTrackingTouch(SeekBar bar) {
|
|
mLastSeekEventTime = 0;
|
|
mFromTouch = true;
|
|
}
|
|
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
|
|
if (!fromuser || (mService == null)) return;
|
|
long now = SystemClock.elapsedRealtime();
|
|
if ((now - mLastSeekEventTime) > 250) {
|
|
mLastSeekEventTime = now;
|
|
mPosOverride = mDuration * progress / 1000;
|
|
try {
|
|
mService.seek(mPosOverride);
|
|
} catch (RemoteException ex) {
|
|
}
|
|
|
|
// trackball event, allow progress updates
|
|
if (!mFromTouch) {
|
|
refreshNow();
|
|
mPosOverride = -1;
|
|
}
|
|
}
|
|
}
|
|
public void onStopTrackingTouch(SeekBar bar) {
|
|
mPosOverride = -1;
|
|
mFromTouch = false;
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mQueueListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
startActivity(new Intent(Intent.ACTION_EDIT)
|
|
.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
|
|
.putExtra("playlist", "nowplaying"));
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mShuffleListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
toggleShuffle();
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mRepeatListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
cycleRepeat();
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mPauseListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
doPauseResume();
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mPrevListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
if (mService == null) return;
|
|
try {
|
|
if (mService.position() < 2000) {
|
|
mService.prev();
|
|
} else {
|
|
mService.seek(0);
|
|
mService.play();
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
};
|
|
|
|
private View.OnClickListener mNextListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
if (mService == null) return;
|
|
try {
|
|
mService.next();
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
};
|
|
|
|
private RepeatingImageButton.RepeatListener mRewListener =
|
|
new RepeatingImageButton.RepeatListener() {
|
|
public void onRepeat(View v, long howlong, int repcnt) {
|
|
scanBackward(repcnt, howlong);
|
|
}
|
|
};
|
|
|
|
private RepeatingImageButton.RepeatListener mFfwdListener =
|
|
new RepeatingImageButton.RepeatListener() {
|
|
public void onRepeat(View v, long howlong, int repcnt) {
|
|
scanForward(repcnt, howlong);
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void onStop() {
|
|
paused = true;
|
|
mHandler.removeMessages(REFRESH);
|
|
unregisterReceiver(mStatusListener);
|
|
MusicUtils.unbindFromService(mToken);
|
|
mService = null;
|
|
super.onStop();
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
paused = false;
|
|
|
|
mToken = MusicUtils.bindToService(this, osc);
|
|
if (mToken == null) {
|
|
// something went wrong
|
|
mHandler.sendEmptyMessage(QUIT);
|
|
}
|
|
|
|
IntentFilter f = new IntentFilter();
|
|
f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
|
|
f.addAction(MediaPlaybackService.META_CHANGED);
|
|
registerReceiver(mStatusListener, new IntentFilter(f));
|
|
updateTrackInfo();
|
|
long next = refreshNow();
|
|
queueNextRefresh(next);
|
|
}
|
|
|
|
@Override
|
|
public void onNewIntent(Intent intent) {
|
|
setIntent(intent);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
updateTrackInfo();
|
|
setPauseButtonImage();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
mAlbumArtWorker.quit();
|
|
super.onDestroy();
|
|
// System.out.println("***************** playback activity onDestroy\n");
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
super.onCreateOptionsMenu(menu);
|
|
// Don't show the menu items if we got launched by path/filedescriptor, or
|
|
// if we're in one shot mode. In most cases, these menu items are not
|
|
// useful in those modes, so for consistency we never show them in these
|
|
// modes, instead of tailoring them to the specific file being played.
|
|
if (MusicUtils.getCurrentAudioId() >= 0) {
|
|
menu.add(0, GOTO_START, 0, R.string.goto_start)
|
|
.setIcon(R.drawable.ic_menu_music_library);
|
|
menu.add(0, PARTY_SHUFFLE, 0,
|
|
R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
|
|
SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist)
|
|
.setIcon(android.R.drawable.ic_menu_add);
|
|
// these next two are in a separate group, so they can be shown/hidden as needed
|
|
// based on the keyguard state
|
|
menu.add(1, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short)
|
|
.setIcon(R.drawable.ic_menu_set_as_ringtone);
|
|
menu.add(1, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
|
|
|
|
Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
|
if (getPackageManager().resolveActivity(i, 0) != null) {
|
|
menu.add(0, EFFECTS_PANEL, 0, R.string.effectspanel).setIcon(R.drawable.ic_menu_eq);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
if (mService == null) return false;
|
|
MenuItem item = menu.findItem(PARTY_SHUFFLE);
|
|
if (item != null) {
|
|
int shuffle = MusicUtils.getCurrentShuffleMode();
|
|
if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
|
|
item.setIcon(R.drawable.ic_menu_party_shuffle);
|
|
item.setTitle(R.string.party_shuffle_off);
|
|
} else {
|
|
item.setIcon(R.drawable.ic_menu_party_shuffle);
|
|
item.setTitle(R.string.party_shuffle);
|
|
}
|
|
}
|
|
|
|
item = menu.findItem(ADD_TO_PLAYLIST);
|
|
if (item != null) {
|
|
SubMenu sub = item.getSubMenu();
|
|
MusicUtils.makePlaylistMenu(this, sub);
|
|
}
|
|
|
|
KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
|
menu.setGroupVisible(1, !km.inKeyguardRestrictedInputMode());
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
Intent intent;
|
|
try {
|
|
switch (item.getItemId()) {
|
|
case GOTO_START:
|
|
intent = new Intent();
|
|
intent.setClass(this, MusicBrowserActivity.class);
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
startActivity(intent);
|
|
finish();
|
|
break;
|
|
case USE_AS_RINGTONE: {
|
|
// Set the system setting to make this the current ringtone
|
|
if (mService != null) {
|
|
MusicUtils.setRingtone(this, mService.getAudioId());
|
|
}
|
|
return true;
|
|
}
|
|
case PARTY_SHUFFLE:
|
|
MusicUtils.togglePartyShuffle();
|
|
setShuffleButtonImage();
|
|
break;
|
|
|
|
case NEW_PLAYLIST: {
|
|
intent = new Intent();
|
|
intent.setClass(this, CreatePlaylist.class);
|
|
startActivityForResult(intent, NEW_PLAYLIST);
|
|
return true;
|
|
}
|
|
|
|
case PLAYLIST_SELECTED: {
|
|
long[] list = new long[1];
|
|
list[0] = MusicUtils.getCurrentAudioId();
|
|
long playlist = item.getIntent().getLongExtra("playlist", 0);
|
|
MusicUtils.addToPlaylist(this, list, playlist);
|
|
return true;
|
|
}
|
|
|
|
case DELETE_ITEM: {
|
|
if (mService != null) {
|
|
long[] list = new long[1];
|
|
list[0] = MusicUtils.getCurrentAudioId();
|
|
Bundle b = new Bundle();
|
|
String f;
|
|
if (android.os.Environment.isExternalStorageRemovable()) {
|
|
f = getString(R.string.delete_song_desc, mService.getTrackName());
|
|
} else {
|
|
f = getString(
|
|
R.string.delete_song_desc_nosdcard, mService.getTrackName());
|
|
}
|
|
b.putString("description", f);
|
|
b.putLongArray("items", list);
|
|
intent = new Intent();
|
|
intent.setClass(this, DeleteItems.class);
|
|
intent.putExtras(b);
|
|
startActivityForResult(intent, -1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case EFFECTS_PANEL: {
|
|
Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
|
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mService.getAudioSessionId());
|
|
startActivityForResult(i, EFFECTS_PANEL);
|
|
return true;
|
|
}
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
if (resultCode != RESULT_OK) {
|
|
return;
|
|
}
|
|
switch (requestCode) {
|
|
case NEW_PLAYLIST:
|
|
Uri uri = intent.getData();
|
|
if (uri != null) {
|
|
long[] list = new long[1];
|
|
list[0] = MusicUtils.getCurrentAudioId();
|
|
int playlist = Integer.parseInt(uri.getLastPathSegment());
|
|
MusicUtils.addToPlaylist(this, list, playlist);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
private final int keyboard[][] = {
|
|
{
|
|
KeyEvent.KEYCODE_Q, KeyEvent.KEYCODE_W, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_R,
|
|
KeyEvent.KEYCODE_T, KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_I,
|
|
KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_P,
|
|
},
|
|
{
|
|
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_F,
|
|
KeyEvent.KEYCODE_G, KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_K,
|
|
KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_DEL,
|
|
},
|
|
{KeyEvent.KEYCODE_Z, KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_V,
|
|
KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_M,
|
|
KeyEvent.KEYCODE_COMMA, KeyEvent.KEYCODE_PERIOD, KeyEvent.KEYCODE_ENTER}
|
|
|
|
};
|
|
|
|
private int lastX;
|
|
private int lastY;
|
|
|
|
private boolean seekMethod1(int keyCode) {
|
|
if (mService == null) return false;
|
|
for (int x = 0; x < 10; x++) {
|
|
for (int y = 0; y < 3; y++) {
|
|
if (keyboard[y][x] == keyCode) {
|
|
int dir = 0;
|
|
// top row
|
|
if (x == lastX && y == lastY)
|
|
dir = 0;
|
|
else if (y == 0 && lastY == 0 && x > lastX)
|
|
dir = 1;
|
|
else if (y == 0 && lastY == 0 && x < lastX)
|
|
dir = -1;
|
|
// bottom row
|
|
else if (y == 2 && lastY == 2 && x > lastX)
|
|
dir = -1;
|
|
else if (y == 2 && lastY == 2 && x < lastX)
|
|
dir = 1;
|
|
// moving up
|
|
else if (y < lastY && x <= 4)
|
|
dir = 1;
|
|
else if (y < lastY && x >= 5)
|
|
dir = -1;
|
|
// moving down
|
|
else if (y > lastY && x <= 4)
|
|
dir = -1;
|
|
else if (y > lastY && x >= 5)
|
|
dir = 1;
|
|
lastX = x;
|
|
lastY = y;
|
|
try {
|
|
mService.seek(mService.position() + dir * 5);
|
|
} catch (RemoteException ex) {
|
|
}
|
|
refreshNow();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
lastX = -1;
|
|
lastY = -1;
|
|
return false;
|
|
}
|
|
|
|
private boolean seekMethod2(int keyCode) {
|
|
if (mService == null) return false;
|
|
for (int i = 0; i < 10; i++) {
|
|
if (keyboard[0][i] == keyCode) {
|
|
int seekpercentage = 100 * i / 10;
|
|
try {
|
|
mService.seek(mService.duration() * seekpercentage / 100);
|
|
} catch (RemoteException ex) {
|
|
}
|
|
refreshNow();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
try {
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
if (!useDpadMusicControl()) {
|
|
break;
|
|
}
|
|
if (mService != null) {
|
|
if (!mSeeking && mStartSeekPos >= 0) {
|
|
mPauseButton.requestFocus();
|
|
if (mStartSeekPos < 1000) {
|
|
mService.prev();
|
|
} else {
|
|
mService.seek(0);
|
|
}
|
|
} else {
|
|
scanBackward(-1, event.getEventTime() - event.getDownTime());
|
|
mPauseButton.requestFocus();
|
|
mStartSeekPos = -1;
|
|
}
|
|
}
|
|
mSeeking = false;
|
|
mPosOverride = -1;
|
|
return true;
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
if (!useDpadMusicControl()) {
|
|
break;
|
|
}
|
|
if (mService != null) {
|
|
if (!mSeeking && mStartSeekPos >= 0) {
|
|
mPauseButton.requestFocus();
|
|
mService.next();
|
|
} else {
|
|
scanForward(-1, event.getEventTime() - event.getDownTime());
|
|
mPauseButton.requestFocus();
|
|
mStartSeekPos = -1;
|
|
}
|
|
}
|
|
mSeeking = false;
|
|
mPosOverride = -1;
|
|
return true;
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
|
|
private boolean useDpadMusicControl() {
|
|
if (mDeviceHasDpad && (mPrevButton.isFocused() || mNextButton.isFocused()
|
|
|| mPauseButton.isFocused())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
int direction = -1;
|
|
int repcnt = event.getRepeatCount();
|
|
|
|
if ((seekmethod == 0) ? seekMethod1(keyCode) : seekMethod2(keyCode)) return true;
|
|
|
|
switch (keyCode) {
|
|
/*
|
|
// image scale
|
|
case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0);
|
|
break;
|
|
// image translate
|
|
case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0);
|
|
break;
|
|
// camera rotation
|
|
case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
|
|
break;
|
|
// camera translate
|
|
case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0);
|
|
break;
|
|
case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
|
break;
|
|
|
|
*/
|
|
|
|
case KeyEvent.KEYCODE_SLASH:
|
|
seekmethod = 1 - seekmethod;
|
|
return true;
|
|
|
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
if (!useDpadMusicControl()) {
|
|
break;
|
|
}
|
|
if (!mPrevButton.hasFocus()) {
|
|
mPrevButton.requestFocus();
|
|
}
|
|
scanBackward(repcnt, event.getEventTime() - event.getDownTime());
|
|
return true;
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
if (!useDpadMusicControl()) {
|
|
break;
|
|
}
|
|
if (!mNextButton.hasFocus()) {
|
|
mNextButton.requestFocus();
|
|
}
|
|
scanForward(repcnt, event.getEventTime() - event.getDownTime());
|
|
return true;
|
|
|
|
case KeyEvent.KEYCODE_S:
|
|
toggleShuffle();
|
|
return true;
|
|
|
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
|
case KeyEvent.KEYCODE_SPACE:
|
|
doPauseResume();
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
private void scanBackward(int repcnt, long delta) {
|
|
if (mService == null) return;
|
|
try {
|
|
if (repcnt == 0) {
|
|
mStartSeekPos = mService.position();
|
|
mLastSeekEventTime = 0;
|
|
mSeeking = false;
|
|
} else {
|
|
mSeeking = true;
|
|
if (delta < 5000) {
|
|
// seek at 10x speed for the first 5 seconds
|
|
delta = delta * 10;
|
|
} else {
|
|
// seek at 40x after that
|
|
delta = 50000 + (delta - 5000) * 40;
|
|
}
|
|
long newpos = mStartSeekPos - delta;
|
|
if (newpos < 0) {
|
|
// move to previous track
|
|
mService.prev();
|
|
long duration = mService.duration();
|
|
mStartSeekPos += duration;
|
|
newpos += duration;
|
|
}
|
|
if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
|
|
mService.seek(newpos);
|
|
mLastSeekEventTime = delta;
|
|
}
|
|
if (repcnt >= 0) {
|
|
mPosOverride = newpos;
|
|
} else {
|
|
mPosOverride = -1;
|
|
}
|
|
refreshNow();
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void scanForward(int repcnt, long delta) {
|
|
if (mService == null) return;
|
|
try {
|
|
if (repcnt == 0) {
|
|
mStartSeekPos = mService.position();
|
|
mLastSeekEventTime = 0;
|
|
mSeeking = false;
|
|
} else {
|
|
mSeeking = true;
|
|
if (delta < 5000) {
|
|
// seek at 10x speed for the first 5 seconds
|
|
delta = delta * 10;
|
|
} else {
|
|
// seek at 40x after that
|
|
delta = 50000 + (delta - 5000) * 40;
|
|
}
|
|
long newpos = mStartSeekPos + delta;
|
|
long duration = mService.duration();
|
|
if (newpos >= duration) {
|
|
// move to next track
|
|
mService.next();
|
|
mStartSeekPos -= duration; // is OK to go negative
|
|
newpos -= duration;
|
|
}
|
|
if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
|
|
mService.seek(newpos);
|
|
mLastSeekEventTime = delta;
|
|
}
|
|
if (repcnt >= 0) {
|
|
mPosOverride = newpos;
|
|
} else {
|
|
mPosOverride = -1;
|
|
}
|
|
refreshNow();
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void doPauseResume() {
|
|
try {
|
|
if (mService != null) {
|
|
if (mService.isPlaying()) {
|
|
mService.pause();
|
|
} else {
|
|
mService.play();
|
|
}
|
|
refreshNow();
|
|
setPauseButtonImage();
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void toggleShuffle() {
|
|
if (mService == null) {
|
|
return;
|
|
}
|
|
try {
|
|
int shuffle = mService.getShuffleMode();
|
|
if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
|
|
mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
|
|
if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
|
|
mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
|
|
setRepeatButtonImage();
|
|
}
|
|
showToast(R.string.shuffle_on_notif);
|
|
} else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL
|
|
|| shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
|
|
mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
|
|
showToast(R.string.shuffle_off_notif);
|
|
} else {
|
|
Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
|
|
}
|
|
setShuffleButtonImage();
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void cycleRepeat() {
|
|
if (mService == null) {
|
|
return;
|
|
}
|
|
try {
|
|
int mode = mService.getRepeatMode();
|
|
if (mode == MediaPlaybackService.REPEAT_NONE) {
|
|
mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
|
|
showToast(R.string.repeat_all_notif);
|
|
} else if (mode == MediaPlaybackService.REPEAT_ALL) {
|
|
mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
|
|
if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
|
|
mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
|
|
setShuffleButtonImage();
|
|
}
|
|
showToast(R.string.repeat_current_notif);
|
|
} else {
|
|
mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
|
|
showToast(R.string.repeat_off_notif);
|
|
}
|
|
setRepeatButtonImage();
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void showToast(int resid) {
|
|
if (mToast == null) {
|
|
mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
|
|
}
|
|
mToast.setText(resid);
|
|
mToast.show();
|
|
}
|
|
|
|
private void startPlayback() {
|
|
if (mService == null) return;
|
|
Intent intent = getIntent();
|
|
String filename = "";
|
|
Uri uri = intent.getData();
|
|
if (uri != null && uri.toString().length() > 0) {
|
|
// If this is a file:// URI, just use the path directly instead
|
|
// of going through the open-from-filedescriptor codepath.
|
|
String scheme = uri.getScheme();
|
|
if ("file".equals(scheme)) {
|
|
filename = uri.getPath();
|
|
} else {
|
|
filename = uri.toString();
|
|
}
|
|
try {
|
|
mService.stop();
|
|
mService.openFile(filename);
|
|
mService.play();
|
|
setIntent(new Intent());
|
|
} catch (Exception ex) {
|
|
Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
|
|
}
|
|
}
|
|
|
|
updateTrackInfo();
|
|
long next = refreshNow();
|
|
queueNextRefresh(next);
|
|
}
|
|
|
|
private ServiceConnection osc = new ServiceConnection() {
|
|
public void onServiceConnected(ComponentName classname, IBinder obj) {
|
|
mService = IMediaPlaybackService.Stub.asInterface(obj);
|
|
startPlayback();
|
|
try {
|
|
// Assume something is playing when the service says it is,
|
|
// but also if the audio ID is valid but the service is paused.
|
|
if (mService.getAudioId() >= 0 || mService.isPlaying()
|
|
|| mService.getPath() != null) {
|
|
// something is playing now, we're done
|
|
mRepeatButton.setVisibility(View.VISIBLE);
|
|
mShuffleButton.setVisibility(View.VISIBLE);
|
|
mQueueButton.setVisibility(View.VISIBLE);
|
|
setRepeatButtonImage();
|
|
setShuffleButtonImage();
|
|
setPauseButtonImage();
|
|
return;
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
// Service is dead or not playing anything. If we got here as part
|
|
// of a "play this file" Intent, exit. Otherwise go to the Music
|
|
// app start screen.
|
|
if (getIntent().getData() == null) {
|
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
|
|
startActivity(intent);
|
|
}
|
|
finish();
|
|
}
|
|
public void onServiceDisconnected(ComponentName classname) {
|
|
mService = null;
|
|
}
|
|
};
|
|
|
|
private void setRepeatButtonImage() {
|
|
if (mService == null) return;
|
|
try {
|
|
switch (mService.getRepeatMode()) {
|
|
case MediaPlaybackService.REPEAT_ALL:
|
|
mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
|
|
break;
|
|
case MediaPlaybackService.REPEAT_CURRENT:
|
|
mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
|
|
break;
|
|
default:
|
|
mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
|
|
break;
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void setShuffleButtonImage() {
|
|
if (mService == null) return;
|
|
try {
|
|
switch (mService.getShuffleMode()) {
|
|
case MediaPlaybackService.SHUFFLE_NONE:
|
|
mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
|
|
break;
|
|
case MediaPlaybackService.SHUFFLE_AUTO:
|
|
mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
|
|
break;
|
|
default:
|
|
mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
|
|
break;
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private void setPauseButtonImage() {
|
|
try {
|
|
if (mService != null && mService.isPlaying()) {
|
|
mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
|
|
} else {
|
|
mPauseButton.setImageResource(android.R.drawable.ic_media_play);
|
|
}
|
|
} catch (RemoteException ex) {
|
|
}
|
|
}
|
|
|
|
private ImageView mAlbum;
|
|
private TextView mCurrentTime;
|
|
private TextView mTotalTime;
|
|
private TextView mArtistName;
|
|
private TextView mAlbumName;
|
|
private TextView mTrackName;
|
|
private ProgressBar mProgress;
|
|
private long mPosOverride = -1;
|
|
private boolean mFromTouch = false;
|
|
private long mDuration;
|
|
private int seekmethod;
|
|
private boolean paused;
|
|
|
|
private static final int REFRESH = 1;
|
|
private static final int QUIT = 2;
|
|
private static final int GET_ALBUM_ART = 3;
|
|
private static final int ALBUM_ART_DECODED = 4;
|
|
|
|
private void queueNextRefresh(long delay) {
|
|
if (!paused) {
|
|
Message msg = mHandler.obtainMessage(REFRESH);
|
|
mHandler.removeMessages(REFRESH);
|
|
mHandler.sendMessageDelayed(msg, delay);
|
|
}
|
|
}
|
|
|
|
private long refreshNow() {
|
|
if (mService == null) return 500;
|
|
try {
|
|
long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
|
|
if ((pos >= 0) && (mDuration > 0)) {
|
|
mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
|
|
int progress = (int) (1000 * pos / mDuration);
|
|
mProgress.setProgress(progress);
|
|
|
|
if (mService.isPlaying()) {
|
|
mCurrentTime.setVisibility(View.VISIBLE);
|
|
} else {
|
|
// blink the counter
|
|
int vis = mCurrentTime.getVisibility();
|
|
mCurrentTime.setVisibility(
|
|
vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
|
|
return 500;
|
|
}
|
|
} else {
|
|
mCurrentTime.setText("--:--");
|
|
mProgress.setProgress(1000);
|
|
}
|
|
// calculate the number of milliseconds until the next full second, so
|
|
// the counter can be updated at just the right time
|
|
long remaining = 1000 - (pos % 1000);
|
|
|
|
// approximate how often we would need to refresh the slider to
|
|
// move it smoothly
|
|
int width = mProgress.getWidth();
|
|
if (width == 0) width = 320;
|
|
long smoothrefreshtime = mDuration / width;
|
|
|
|
if (smoothrefreshtime > remaining) return remaining;
|
|
if (smoothrefreshtime < 20) return 20;
|
|
return smoothrefreshtime;
|
|
} catch (RemoteException ex) {
|
|
}
|
|
return 500;
|
|
}
|
|
|
|
private final Handler mHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
case ALBUM_ART_DECODED:
|
|
mAlbum.setImageBitmap((Bitmap) msg.obj);
|
|
mAlbum.getDrawable().setDither(true);
|
|
break;
|
|
|
|
case REFRESH:
|
|
long next = refreshNow();
|
|
queueNextRefresh(next);
|
|
break;
|
|
|
|
case QUIT:
|
|
// This can be moved back to onCreate once the bug that prevents
|
|
// Dialogs from being started from onCreate/onResume is fixed.
|
|
new AlertDialog.Builder(MediaPlaybackActivity.this)
|
|
.setTitle(R.string.service_start_error_title)
|
|
.setMessage(R.string.service_start_error_msg)
|
|
.setPositiveButton(R.string.service_start_error_button,
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(
|
|
DialogInterface dialog, int whichButton) {
|
|
finish();
|
|
}
|
|
})
|
|
.setCancelable(false)
|
|
.show();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
if (action.equals(MediaPlaybackService.META_CHANGED)) {
|
|
// redraw the artist/title info and
|
|
// set new max for progress bar
|
|
updateTrackInfo();
|
|
setPauseButtonImage();
|
|
queueNextRefresh(1);
|
|
} else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
|
|
setPauseButtonImage();
|
|
}
|
|
}
|
|
};
|
|
|
|
private static class AlbumSongIdWrapper {
|
|
public long albumid;
|
|
public long songid;
|
|
AlbumSongIdWrapper(long aid, long sid) {
|
|
albumid = aid;
|
|
songid = sid;
|
|
}
|
|
}
|
|
|
|
private void updateTrackInfo() {
|
|
if (mService == null) {
|
|
return;
|
|
}
|
|
try {
|
|
String path = mService.getPath();
|
|
if (path == null) {
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
long songid = mService.getAudioId();
|
|
if (songid < 0 && path.toLowerCase().startsWith("http://")) {
|
|
// Once we can get album art and meta data from MediaPlayer, we
|
|
// can show that info again when streaming.
|
|
((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
|
|
((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
|
|
mAlbum.setVisibility(View.GONE);
|
|
mTrackName.setText(path);
|
|
mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
|
|
mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(-1, -1))
|
|
.sendToTarget();
|
|
} else {
|
|
((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
|
|
((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
|
|
String artistName = mService.getArtistName();
|
|
if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
|
|
artistName = getString(R.string.unknown_artist_name);
|
|
}
|
|
mArtistName.setText(artistName);
|
|
String albumName = mService.getAlbumName();
|
|
long albumid = mService.getAlbumId();
|
|
if (MediaStore.UNKNOWN_STRING.equals(albumName)) {
|
|
albumName = getString(R.string.unknown_album_name);
|
|
albumid = -1;
|
|
}
|
|
mAlbumName.setText(albumName);
|
|
mTrackName.setText(mService.getTrackName());
|
|
mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
|
|
mAlbumArtHandler
|
|
.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(albumid, songid))
|
|
.sendToTarget();
|
|
mAlbum.setVisibility(View.VISIBLE);
|
|
}
|
|
mDuration = mService.duration();
|
|
mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
|
|
} catch (RemoteException ex) {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
public class AlbumArtHandler extends Handler {
|
|
private long mAlbumId = -1;
|
|
|
|
public AlbumArtHandler(Looper looper) {
|
|
super(looper);
|
|
}
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
long albumid = ((AlbumSongIdWrapper) msg.obj).albumid;
|
|
long songid = ((AlbumSongIdWrapper) msg.obj).songid;
|
|
if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
|
|
// while decoding the new image, show the default album art
|
|
Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
|
|
mHandler.removeMessages(ALBUM_ART_DECODED);
|
|
mHandler.sendMessageDelayed(numsg, 300);
|
|
// Don't allow default artwork here, because we want to fall back to song-specific
|
|
// album art if we can't find anything for the album.
|
|
Bitmap bm =
|
|
MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, albumid, false);
|
|
if (bm == null) {
|
|
bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, -1);
|
|
albumid = -1;
|
|
}
|
|
if (bm != null) {
|
|
numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
|
|
mHandler.removeMessages(ALBUM_ART_DECODED);
|
|
mHandler.sendMessage(numsg);
|
|
}
|
|
mAlbumId = albumid;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class Worker implements Runnable {
|
|
private final Object mLock = new Object();
|
|
private Looper mLooper;
|
|
|
|
/**
|
|
* Creates a worker thread with the given name. The thread
|
|
* then runs a {@link android.os.Looper}.
|
|
* @param name A name for the new thread
|
|
*/
|
|
Worker(String name) {
|
|
Thread t = new Thread(null, this, name);
|
|
t.setPriority(Thread.MIN_PRIORITY);
|
|
t.start();
|
|
synchronized (mLock) {
|
|
while (mLooper == null) {
|
|
try {
|
|
mLock.wait();
|
|
} catch (InterruptedException ex) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Looper getLooper() {
|
|
return mLooper;
|
|
}
|
|
|
|
public void run() {
|
|
synchronized (mLock) {
|
|
Looper.prepare();
|
|
mLooper = Looper.myLooper();
|
|
mLock.notifyAll();
|
|
}
|
|
Looper.loop();
|
|
}
|
|
|
|
public void quit() {
|
|
mLooper.quit();
|
|
}
|
|
}
|
|
}
|