| /* |
| * 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(); |
| } |
| } |
| } |
| |