blob: 2f049783c18a648306ac2f095737540736f4bdb8 [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.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();
}
}
}