blob: 1f7b4049b2ccbdcdd54b83d2c4c9de69e6752b35 [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.music;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.SearchManager;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29import android.graphics.Bitmap;
30import android.media.AudioManager;
31import android.media.MediaFile;
32import android.net.Uri;
33import android.os.Bundle;
34import android.os.RemoteException;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Looper;
38import android.os.Message;
39import android.os.SystemClock;
40import android.provider.MediaStore;
41import android.text.Layout;
42import android.text.TextUtils.TruncateAt;
43import android.util.Log;
44import android.view.KeyEvent;
45import android.view.Menu;
46import android.view.MenuItem;
47import android.view.MotionEvent;
48import android.view.SubMenu;
49import android.view.View;
50import android.view.ViewConfiguration;
51import android.view.Window;
52import android.widget.ImageButton;
53import android.widget.ImageView;
54import android.widget.ProgressBar;
55import android.widget.SeekBar;
56import android.widget.TextView;
57import android.widget.Toast;
58import android.widget.SeekBar.OnSeekBarChangeListener;
59
60
61public class MediaPlaybackActivity extends Activity implements MusicUtils.Defs,
62 View.OnTouchListener, View.OnLongClickListener
63{
64 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
65
66 private boolean mOneShot = false;
67 private boolean mSeeking = false;
68 private boolean mTrackball;
69 private long mStartSeekPos = 0;
70 private long mLastSeekEventTime;
71 private IMediaPlaybackService mService = null;
72 private RepeatingImageButton mPrevButton;
73 private ImageButton mPauseButton;
74 private RepeatingImageButton mNextButton;
75 private ImageButton mRepeatButton;
76 private ImageButton mShuffleButton;
77 private ImageButton mQueueButton;
78 private Worker mAlbumArtWorker;
79 private AlbumArtHandler mAlbumArtHandler;
80 private Toast mToast;
81 private boolean mRelaunchAfterConfigChange;
82 private int mTouchSlop;
83
84 public MediaPlaybackActivity()
85 {
86 }
87
88 /** Called when the activity is first created. */
89 @Override
90 public void onCreate(Bundle icicle)
91 {
92 super.onCreate(icicle);
93 setVolumeControlStream(AudioManager.STREAM_MUSIC);
94
95 mAlbumArtWorker = new Worker("album art worker");
96 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
97
98 requestWindowFeature(Window.FEATURE_NO_TITLE);
99 setContentView(R.layout.audio_player);
100
101 mCurrentTime = (TextView) findViewById(R.id.currenttime);
102 mTotalTime = (TextView) findViewById(R.id.totaltime);
103 mProgress = (ProgressBar) findViewById(android.R.id.progress);
104 mAlbum = (ImageView) findViewById(R.id.album);
105 mArtistName = (TextView) findViewById(R.id.artistname);
106 mAlbumName = (TextView) findViewById(R.id.albumname);
107 mTrackName = (TextView) findViewById(R.id.trackname);
108
109 View v = (View)mArtistName.getParent();
110 v.setOnTouchListener(this);
111 v.setOnLongClickListener(this);
112
113 v = (View)mAlbumName.getParent();
114 v.setOnTouchListener(this);
115 v.setOnLongClickListener(this);
116
117 v = (View)mTrackName.getParent();
118 v.setOnTouchListener(this);
119 v.setOnLongClickListener(this);
120
121 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
122 mPrevButton.setOnClickListener(mPrevListener);
123 mPrevButton.setRepeatListener(mRewListener, 260);
124 mPauseButton = (ImageButton) findViewById(R.id.pause);
125 mPauseButton.requestFocus();
126 mPauseButton.setOnClickListener(mPauseListener);
127 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
128 mNextButton.setOnClickListener(mNextListener);
129 mNextButton.setRepeatListener(mFfwdListener, 260);
130 seekmethod = 1;
131
132 mTrackball = true; /* (See bug 1044348) (getResources().getConfiguration().navigation ==
133 Resources.Configuration.NAVIGATION_TRACKBALL);*/
134
135 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
136 mQueueButton.setOnClickListener(mQueueListener);
137 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
138 mShuffleButton.setOnClickListener(mShuffleListener);
139 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
140 mRepeatButton.setOnClickListener(mRepeatListener);
141
142 if (mProgress instanceof SeekBar) {
143 SeekBar seeker = (SeekBar) mProgress;
144 seeker.setOnSeekBarChangeListener(mSeekListener);
145 }
146 mProgress.setMax(1000);
147
148 if (icicle != null) {
149 mRelaunchAfterConfigChange = icicle.getBoolean("configchange");
150 mOneShot = icicle.getBoolean("oneshot");
151 } else {
152 mOneShot = getIntent().getBooleanExtra("oneshot", false);
153 }
154
155 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
156 }
157
158 int mInitialX = -1;
159 int mLastX = -1;
160 int mTextWidth = 0;
161 int mViewWidth = 0;
162 boolean mDraggingLabel = false;
163
164 TextView textViewForContainer(View v) {
165 View vv = v.findViewById(R.id.artistname);
166 if (vv != null) return (TextView) vv;
167 vv = v.findViewById(R.id.albumname);
168 if (vv != null) return (TextView) vv;
169 vv = v.findViewById(R.id.trackname);
170 if (vv != null) return (TextView) vv;
171 return null;
172 }
173
174 public boolean onTouch(View v, MotionEvent event) {
175 int action = event.getAction();
176 TextView tv = textViewForContainer(v);
177 if (tv == null) {
178 return false;
179 }
180 if (action == MotionEvent.ACTION_DOWN) {
181 v.setBackgroundColor(0xff606060);
182 mInitialX = mLastX = (int) event.getX();
183 mDraggingLabel = false;
184 } else if (action == MotionEvent.ACTION_UP ||
185 action == MotionEvent.ACTION_CANCEL) {
186 v.setBackgroundColor(0);
187 if (mDraggingLabel) {
188 Message msg = mLabelScroller.obtainMessage(0, tv);
189 mLabelScroller.sendMessageDelayed(msg, 1000);
190 }
191 } else if (action == MotionEvent.ACTION_MOVE) {
192 if (mDraggingLabel) {
193 int scrollx = tv.getScrollX();
194 int x = (int) event.getX();
195 int delta = mLastX - x;
196 if (delta != 0) {
197 mLastX = x;
198 scrollx += delta;
199 if (scrollx > mTextWidth) {
200 // scrolled the text completely off the view to the left
201 scrollx -= mTextWidth;
202 scrollx -= mViewWidth;
203 }
204 if (scrollx < -mViewWidth) {
205 // scrolled the text completely off the view to the right
206 scrollx += mViewWidth;
207 scrollx += mTextWidth;
208 }
209 tv.scrollTo(scrollx, 0);
210 }
211 return true;
212 }
213 int delta = mInitialX - (int) event.getX();
214 if (Math.abs(delta) > mTouchSlop) {
215 // start moving
216 mLabelScroller.removeMessages(0, tv);
217
218 // Only turn ellipsizing off when it's not already off, because it
219 // causes the scroll position to be reset to 0.
220 if (tv.getEllipsize() != null) {
221 tv.setEllipsize(null);
222 }
223 Layout ll = tv.getLayout();
224 // layout might be null if the text just changed, or ellipsizing
225 // was just turned off
226 if (ll == null) {
227 return false;
228 }
229 // get the non-ellipsized line width, to determine whether scrolling
230 // should even be allowed
231 mTextWidth = (int) tv.getLayout().getLineWidth(0);
232 mViewWidth = tv.getWidth();
233 if (mViewWidth > mTextWidth) {
234 tv.setEllipsize(TruncateAt.END);
235 v.cancelLongPress();
236 return false;
237 }
238 mDraggingLabel = true;
239 tv.setHorizontalFadingEdgeEnabled(true);
240 v.cancelLongPress();
241 return true;
242 }
243 }
244 return false;
245 }
246
247 Handler mLabelScroller = new Handler() {
248 @Override
249 public void handleMessage(Message msg) {
250 TextView tv = (TextView) msg.obj;
251 int x = tv.getScrollX();
252 x = x * 3 / 4;
253 tv.scrollTo(x, 0);
254 if (x == 0) {
255 tv.setEllipsize(TruncateAt.END);
256 } else {
257 Message newmsg = obtainMessage(0, tv);
258 mLabelScroller.sendMessageDelayed(newmsg, 15);
259 }
260 }
261 };
262
263 public boolean onLongClick(View view) {
264
265 CharSequence title = null;
266 String mime = null;
267 String query = null;
268 String artist;
269 String album;
270 String song;
271
272 try {
273 artist = mService.getArtistName();
274 album = mService.getAlbumName();
275 song = mService.getTrackName();
276 } catch (RemoteException ex) {
277 return true;
278 }
279
Andreas Huberc8398492009-04-13 16:15:57 -0700280 boolean knownartist =
281 (artist != null) && !MediaFile.UNKNOWN_STRING.equals(artist);
282
283 boolean knownalbum =
284 (album != null) && !MediaFile.UNKNOWN_STRING.equals(album);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800285
286 if (knownartist && view.equals(mArtistName.getParent())) {
287 title = artist;
288 query = artist.toString();
289 mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
290 } else if (knownalbum && view.equals(mAlbumName.getParent())) {
291 title = album;
292 if (knownartist) {
293 query = artist + " " + album;
294 } else {
295 query = album;
296 }
297 mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
298 } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
Andreas Huberc8398492009-04-13 16:15:57 -0700299 if ((song == null) || MediaFile.UNKNOWN_STRING.equals(song)) {
300 // A popup of the form "Search for null/'' using ..." is pretty
301 // unhelpful, plus, we won't find any way to buy it anyway.
302 return true;
303 }
304
The Android Open Source Project792a2202009-03-03 19:32:30 -0800305 title = song;
306 if (knownartist) {
307 query = artist + " " + song;
308 } else {
309 query = song;
310 }
311 mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
312 } else {
313 throw new RuntimeException("shouldn't be here");
314 }
315 title = getString(R.string.mediasearch, title);
316
317 Intent i = new Intent();
318 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
319 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
320 i.putExtra(SearchManager.QUERY, query);
321 if(knownartist) {
322 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
323 }
324 if(knownalbum) {
325 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
326 }
327 i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
328 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
329
330 startActivity(Intent.createChooser(i, title));
331 return true;
332 }
333
334 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
335 public void onStartTrackingTouch(SeekBar bar) {
336 mLastSeekEventTime = 0;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700337 mFromTouch = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800338 }
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700339 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
340 if (!fromuser || (mService == null)) return;
341 long now = SystemClock.elapsedRealtime();
342 if ((now - mLastSeekEventTime) > 250) {
343 mLastSeekEventTime = now;
344 mPosOverride = mDuration * progress / 1000;
345 try {
346 mService.seek(mPosOverride);
347 } catch (RemoteException ex) {
348 }
349
350 // trackball event, allow progress updates
351 if (!mFromTouch) {
352 refreshNow();
353 mPosOverride = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800354 }
355 }
356 }
357 public void onStopTrackingTouch(SeekBar bar) {
358 mPosOverride = -1;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700359 mFromTouch = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800360 }
361 };
362
363 private View.OnClickListener mQueueListener = new View.OnClickListener() {
364 public void onClick(View v) {
365 startActivity(
366 new Intent(Intent.ACTION_EDIT)
367 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
368 .putExtra("playlist", "nowplaying")
369 );
370 }
371 };
372
373 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
374 public void onClick(View v) {
375 toggleShuffle();
376 }
377 };
378
379 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
380 public void onClick(View v) {
381 cycleRepeat();
382 }
383 };
384
385 private View.OnClickListener mPauseListener = new View.OnClickListener() {
386 public void onClick(View v) {
387 doPauseResume();
388 }
389 };
390
391 private View.OnClickListener mPrevListener = new View.OnClickListener() {
392 public void onClick(View v) {
393 if (mService == null) return;
394 try {
395 if (mService.position() < 2000) {
396 mService.prev();
397 } else {
398 mService.seek(0);
399 mService.play();
400 }
401 } catch (RemoteException ex) {
402 }
403 }
404 };
405
406 private View.OnClickListener mNextListener = new View.OnClickListener() {
407 public void onClick(View v) {
408 if (mService == null) return;
409 try {
410 mService.next();
411 } catch (RemoteException ex) {
412 }
413 }
414 };
415
416 private RepeatingImageButton.RepeatListener mRewListener =
417 new RepeatingImageButton.RepeatListener() {
418 public void onRepeat(View v, long howlong, int repcnt) {
419 scanBackward(repcnt, howlong);
420 }
421 };
422
423 private RepeatingImageButton.RepeatListener mFfwdListener =
424 new RepeatingImageButton.RepeatListener() {
425 public void onRepeat(View v, long howlong, int repcnt) {
426 scanForward(repcnt, howlong);
427 }
428 };
429
430 @Override
431 public void onStop() {
432 paused = true;
433 if (mService != null && mOneShot && getChangingConfigurations() == 0) {
434 try {
435 mService.stop();
436 } catch (RemoteException ex) {
437 }
438 }
439 mHandler.removeMessages(REFRESH);
440 unregisterReceiver(mStatusListener);
441 MusicUtils.unbindFromService(this);
442 super.onStop();
443 }
444
445 @Override
446 public void onSaveInstanceState(Bundle outState) {
447 outState.putBoolean("configchange", getChangingConfigurations() != 0);
448 outState.putBoolean("oneshot", mOneShot);
449 super.onSaveInstanceState(outState);
450 }
451
452 @Override
453 public void onStart() {
454 super.onStart();
455 paused = false;
456
457 if (false == MusicUtils.bindToService(this, osc)) {
458 // something went wrong
459 mHandler.sendEmptyMessage(QUIT);
460 }
461
462 IntentFilter f = new IntentFilter();
463 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
464 f.addAction(MediaPlaybackService.META_CHANGED);
465 f.addAction(MediaPlaybackService.PLAYBACK_COMPLETE);
466 registerReceiver(mStatusListener, new IntentFilter(f));
467 updateTrackInfo();
468 long next = refreshNow();
469 queueNextRefresh(next);
470 }
471
472 @Override
473 public void onNewIntent(Intent intent) {
474 setIntent(intent);
475 mOneShot = intent.getBooleanExtra("oneshot", false);
476 }
477
478 @Override
479 public void onResume() {
480 super.onResume();
481 updateTrackInfo();
482 setPauseButtonImage();
483 }
484
485 @Override
486 public void onDestroy()
487 {
488 mAlbumArtWorker.quit();
489 super.onDestroy();
490 //System.out.println("***************** playback activity onDestroy\n");
491 }
492
493 @Override
494 public boolean onCreateOptionsMenu(Menu menu) {
495 super.onCreateOptionsMenu(menu);
496 // Don't show the menu items if we got launched by path/filedescriptor, since
497 // those tend to not be in the media database.
498 if (MusicUtils.getCurrentAudioId() >= 0) {
499 if (!mOneShot) {
500 menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
501 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
502 }
503 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0,
504 R.string.add_to_playlist).setIcon(android.R.drawable.ic_menu_add);
505 MusicUtils.makePlaylistMenu(this, sub);
506 menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short).setIcon(R.drawable.ic_menu_set_as_ringtone);
507 menu.add(0, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
508 }
509 return true;
510 }
511
512 @Override
513 public boolean onPrepareOptionsMenu(Menu menu) {
514 MenuItem item = menu.findItem(PARTY_SHUFFLE);
515 if (item != null) {
516 int shuffle = MusicUtils.getCurrentShuffleMode();
517 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
518 item.setIcon(R.drawable.ic_menu_party_shuffle);
519 item.setTitle(R.string.party_shuffle_off);
520 } else {
521 item.setIcon(R.drawable.ic_menu_party_shuffle);
522 item.setTitle(R.string.party_shuffle);
523 }
524 }
525 return true;
526 }
527
528 @Override
529 public boolean onOptionsItemSelected(MenuItem item) {
530 Intent intent;
531 try {
532 switch (item.getItemId()) {
533 case GOTO_START:
534 intent = new Intent();
535 intent.setClass(this, MusicBrowserActivity.class);
536 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
537 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
538 startActivity(intent);
539 break;
540 case USE_AS_RINGTONE: {
541 // Set the system setting to make this the current ringtone
542 if (mService != null) {
543 MusicUtils.setRingtone(this, mService.getAudioId());
544 }
545 return true;
546 }
547 case PARTY_SHUFFLE:
548 if (mService != null) {
549 int shuffle = mService.getShuffleMode();
550 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
551 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
552 } else {
553 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
554 }
555 }
556 setShuffleButtonImage();
557 break;
558
559 case NEW_PLAYLIST: {
560 intent = new Intent();
561 intent.setClass(this, CreatePlaylist.class);
562 startActivityForResult(intent, NEW_PLAYLIST);
563 return true;
564 }
565
566 case PLAYLIST_SELECTED: {
567 int [] list = new int[1];
568 list[0] = MusicUtils.getCurrentAudioId();
569 int playlist = item.getIntent().getIntExtra("playlist", 0);
570 MusicUtils.addToPlaylist(this, list, playlist);
571 return true;
572 }
573
574 case DELETE_ITEM: {
575 if (mService != null) {
576 int [] list = new int[1];
577 list[0] = MusicUtils.getCurrentAudioId();
578 Bundle b = new Bundle();
579 b.putString("description", getString(R.string.delete_song_desc,
580 mService.getTrackName()));
581 b.putIntArray("items", list);
582 intent = new Intent();
583 intent.setClass(this, DeleteItems.class);
584 intent.putExtras(b);
585 startActivityForResult(intent, -1);
586 }
587 return true;
588 }
589 }
590 } catch (RemoteException ex) {
591 }
592 return super.onOptionsItemSelected(item);
593 }
594
595 @Override
596 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
597 if (resultCode != RESULT_OK) {
598 return;
599 }
600 switch (requestCode) {
601 case NEW_PLAYLIST:
602 Uri uri = intent.getData();
603 if (uri != null) {
604 int [] list = new int[1];
605 list[0] = MusicUtils.getCurrentAudioId();
606 int playlist = Integer.parseInt(uri.getLastPathSegment());
607 MusicUtils.addToPlaylist(this, list, playlist);
608 }
609 break;
610 }
611 }
612 private final int keyboard[][] = {
613 {
614 KeyEvent.KEYCODE_Q,
615 KeyEvent.KEYCODE_W,
616 KeyEvent.KEYCODE_E,
617 KeyEvent.KEYCODE_R,
618 KeyEvent.KEYCODE_T,
619 KeyEvent.KEYCODE_Y,
620 KeyEvent.KEYCODE_U,
621 KeyEvent.KEYCODE_I,
622 KeyEvent.KEYCODE_O,
623 KeyEvent.KEYCODE_P,
624 },
625 {
626 KeyEvent.KEYCODE_A,
627 KeyEvent.KEYCODE_S,
628 KeyEvent.KEYCODE_D,
629 KeyEvent.KEYCODE_F,
630 KeyEvent.KEYCODE_G,
631 KeyEvent.KEYCODE_H,
632 KeyEvent.KEYCODE_J,
633 KeyEvent.KEYCODE_K,
634 KeyEvent.KEYCODE_L,
635 KeyEvent.KEYCODE_DEL,
636 },
637 {
638 KeyEvent.KEYCODE_Z,
639 KeyEvent.KEYCODE_X,
640 KeyEvent.KEYCODE_C,
641 KeyEvent.KEYCODE_V,
642 KeyEvent.KEYCODE_B,
643 KeyEvent.KEYCODE_N,
644 KeyEvent.KEYCODE_M,
645 KeyEvent.KEYCODE_COMMA,
646 KeyEvent.KEYCODE_PERIOD,
647 KeyEvent.KEYCODE_ENTER
648 }
649
650 };
651
652 private int lastX;
653 private int lastY;
654
655 private boolean seekMethod1(int keyCode)
656 {
657 for(int x=0;x<10;x++) {
658 for(int y=0;y<3;y++) {
659 if(keyboard[y][x] == keyCode) {
660 int dir = 0;
661 // top row
662 if(x == lastX && y == lastY) dir = 0;
663 else if (y == 0 && lastY == 0 && x > lastX) dir = 1;
664 else if (y == 0 && lastY == 0 && x < lastX) dir = -1;
665 // bottom row
666 else if (y == 2 && lastY == 2 && x > lastX) dir = -1;
667 else if (y == 2 && lastY == 2 && x < lastX) dir = 1;
668 // moving up
669 else if (y < lastY && x <= 4) dir = 1;
670 else if (y < lastY && x >= 5) dir = -1;
671 // moving down
672 else if (y > lastY && x <= 4) dir = -1;
673 else if (y > lastY && x >= 5) dir = 1;
674 lastX = x;
675 lastY = y;
676 try {
677 mService.seek(mService.position() + dir * 5);
678 } catch (RemoteException ex) {
679 }
680 refreshNow();
681 return true;
682 }
683 }
684 }
685 lastX = -1;
686 lastY = -1;
687 return false;
688 }
689
690 private boolean seekMethod2(int keyCode)
691 {
692 if (mService == null) return false;
693 for(int i=0;i<10;i++) {
694 if(keyboard[0][i] == keyCode) {
695 int seekpercentage = 100*i/10;
696 try {
697 mService.seek(mService.duration() * seekpercentage / 100);
698 } catch (RemoteException ex) {
699 }
700 refreshNow();
701 return true;
702 }
703 }
704 return false;
705 }
706
707 @Override
708 public boolean onKeyUp(int keyCode, KeyEvent event) {
709 try {
710 switch(keyCode)
711 {
712 case KeyEvent.KEYCODE_DPAD_LEFT:
713 if (mTrackball) {
714 break;
715 }
716 if (mService != null) {
717 if (!mSeeking && mStartSeekPos >= 0) {
718 mPauseButton.requestFocus();
719 if (mStartSeekPos < 1000) {
720 mService.prev();
721 } else {
722 mService.seek(0);
723 }
724 } else {
725 scanBackward(-1, event.getEventTime() - event.getDownTime());
726 mPauseButton.requestFocus();
727 mStartSeekPos = -1;
728 }
729 }
730 mSeeking = false;
731 mPosOverride = -1;
732 return true;
733 case KeyEvent.KEYCODE_DPAD_RIGHT:
734 if (mTrackball) {
735 break;
736 }
737 if (mService != null) {
738 if (!mSeeking && mStartSeekPos >= 0) {
739 mPauseButton.requestFocus();
740 mService.next();
741 } else {
742 scanForward(-1, event.getEventTime() - event.getDownTime());
743 mPauseButton.requestFocus();
744 mStartSeekPos = -1;
745 }
746 }
747 mSeeking = false;
748 mPosOverride = -1;
749 return true;
750 }
751 } catch (RemoteException ex) {
752 }
753 return super.onKeyUp(keyCode, event);
754 }
755
756 @Override
757 public boolean onKeyDown(int keyCode, KeyEvent event)
758 {
759 int direction = -1;
760 int repcnt = event.getRepeatCount();
761
762 if((seekmethod==0)?seekMethod1(keyCode):seekMethod2(keyCode))
763 return true;
764
765 switch(keyCode)
766 {
767/*
768 // image scale
769 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0); break;
770 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0); break;
771 // image translate
772 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0); break;
773 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break;
774 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0); break;
775 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0); break;
776 // camera rotation
777 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0); break;
778 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); break;
779 // camera translate
780 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0); break;
781 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); break;
782 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0); break;
783 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0); break;
784
785*/
786
787 case KeyEvent.KEYCODE_SLASH:
788 seekmethod = 1 - seekmethod;
789 return true;
790
791 case KeyEvent.KEYCODE_DPAD_LEFT:
792 if (mTrackball) {
793 break;
794 }
795 if (!mPrevButton.hasFocus()) {
796 mPrevButton.requestFocus();
797 }
798 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
799 return true;
800 case KeyEvent.KEYCODE_DPAD_RIGHT:
801 if (mTrackball) {
802 break;
803 }
804 if (!mNextButton.hasFocus()) {
805 mNextButton.requestFocus();
806 }
807 scanForward(repcnt, event.getEventTime() - event.getDownTime());
808 return true;
809
810 case KeyEvent.KEYCODE_S:
811 toggleShuffle();
812 return true;
813
814 case KeyEvent.KEYCODE_DPAD_CENTER:
815 case KeyEvent.KEYCODE_SPACE:
816 doPauseResume();
817 return true;
818 }
819 return super.onKeyDown(keyCode, event);
820 }
821
822 private void scanBackward(int repcnt, long delta) {
823 if(mService == null) return;
824 try {
825 if(repcnt == 0) {
826 mStartSeekPos = mService.position();
827 mLastSeekEventTime = 0;
828 mSeeking = false;
829 } else {
830 mSeeking = true;
831 if (delta < 5000) {
832 // seek at 10x speed for the first 5 seconds
833 delta = delta * 10;
834 } else {
835 // seek at 40x after that
836 delta = 50000 + (delta - 5000) * 40;
837 }
838 long newpos = mStartSeekPos - delta;
839 if (newpos < 0) {
840 // move to previous track
841 mService.prev();
842 long duration = mService.duration();
843 mStartSeekPos += duration;
844 newpos += duration;
845 }
846 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
847 mService.seek(newpos);
848 mLastSeekEventTime = delta;
849 }
850 if (repcnt >= 0) {
851 mPosOverride = newpos;
852 } else {
853 mPosOverride = -1;
854 }
855 refreshNow();
856 }
857 } catch (RemoteException ex) {
858 }
859 }
860
861 private void scanForward(int repcnt, long delta) {
862 if(mService == null) return;
863 try {
864 if(repcnt == 0) {
865 mStartSeekPos = mService.position();
866 mLastSeekEventTime = 0;
867 mSeeking = false;
868 } else {
869 mSeeking = true;
870 if (delta < 5000) {
871 // seek at 10x speed for the first 5 seconds
872 delta = delta * 10;
873 } else {
874 // seek at 40x after that
875 delta = 50000 + (delta - 5000) * 40;
876 }
877 long newpos = mStartSeekPos + delta;
878 long duration = mService.duration();
879 if (newpos >= duration) {
880 // move to next track
881 mService.next();
882 mStartSeekPos -= duration; // is OK to go negative
883 newpos -= duration;
884 }
885 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0){
886 mService.seek(newpos);
887 mLastSeekEventTime = delta;
888 }
889 if (repcnt >= 0) {
890 mPosOverride = newpos;
891 } else {
892 mPosOverride = -1;
893 }
894 refreshNow();
895 }
896 } catch (RemoteException ex) {
897 }
898 }
899
900 private void doPauseResume() {
901 try {
902 if(mService != null) {
903 if (mService.isPlaying()) {
904 mService.pause();
905 } else {
906 mService.play();
907 }
908 refreshNow();
909 setPauseButtonImage();
910 }
911 } catch (RemoteException ex) {
912 }
913 }
914
915 private void toggleShuffle() {
916 if (mService == null) {
917 return;
918 }
919 try {
920 int shuffle = mService.getShuffleMode();
921 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
922 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
923 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
924 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
925 setRepeatButtonImage();
926 }
927 showToast(R.string.shuffle_on_notif);
928 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL ||
929 shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
930 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
931 showToast(R.string.shuffle_off_notif);
932 } else {
933 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
934 }
935 setShuffleButtonImage();
936 } catch (RemoteException ex) {
937 }
938 }
939
940 private void cycleRepeat() {
941 if (mService == null) {
942 return;
943 }
944 try {
945 int mode = mService.getRepeatMode();
946 if (mode == MediaPlaybackService.REPEAT_NONE) {
947 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
948 showToast(R.string.repeat_all_notif);
949 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
950 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
951 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
952 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
953 setShuffleButtonImage();
954 }
955 showToast(R.string.repeat_current_notif);
956 } else {
957 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
958 showToast(R.string.repeat_off_notif);
959 }
960 setRepeatButtonImage();
961 } catch (RemoteException ex) {
962 }
963
964 }
965
966 private void showToast(int resid) {
967 if (mToast == null) {
968 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
969 }
970 mToast.setText(resid);
971 mToast.show();
972 }
973
974 private void startPlayback() {
975
976 if(mService == null)
977 return;
978 Intent intent = getIntent();
979 String filename = "";
980 Uri uri = intent.getData();
981 if (uri != null && uri.toString().length() > 0) {
982 // If this is a file:// URI, just use the path directly instead
983 // of going through the open-from-filedescriptor codepath.
984 String scheme = uri.getScheme();
985 if ("file".equals(scheme)) {
986 filename = uri.getPath();
987 } else {
988 filename = uri.toString();
989 }
990 try {
991 mOneShot = true;
992 if (! mRelaunchAfterConfigChange) {
993 mService.stop();
994 mService.openfile(filename);
995 mService.play();
996 }
997 } catch (Exception ex) {
998 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
999 }
1000 }
1001
1002 updateTrackInfo();
1003 long next = refreshNow();
1004 queueNextRefresh(next);
1005 }
1006
1007 private ServiceConnection osc = new ServiceConnection() {
1008 public void onServiceConnected(ComponentName classname, IBinder obj) {
1009 mService = IMediaPlaybackService.Stub.asInterface(obj);
1010 if (MusicUtils.sService == null) {
1011 MusicUtils.sService = mService;
1012 }
1013 startPlayback();
1014 try {
1015 // Assume something is playing when the service says it is,
1016 // but also if the audio ID is valid but the service is paused.
1017 if (mService.getAudioId() >= 0 || mService.isPlaying() ||
1018 mService.getPath() != null) {
1019 // something is playing now, we're done
1020 if (mOneShot || mService.getAudioId() < 0) {
1021 mRepeatButton.setVisibility(View.INVISIBLE);
1022 mShuffleButton.setVisibility(View.INVISIBLE);
1023 mQueueButton.setVisibility(View.INVISIBLE);
1024 } else {
1025 mRepeatButton.setVisibility(View.VISIBLE);
1026 mShuffleButton.setVisibility(View.VISIBLE);
1027 mQueueButton.setVisibility(View.VISIBLE);
1028 setRepeatButtonImage();
1029 setShuffleButtonImage();
1030 }
1031 setPauseButtonImage();
1032 return;
1033 }
1034 } catch (RemoteException ex) {
1035 }
1036 // Service is dead or not playing anything. If we got here as part
1037 // of a "play this file" Intent, exit. Otherwise go to the Music
1038 // app start screen.
1039 if (getIntent().getData() == null) {
1040 Intent intent = new Intent(Intent.ACTION_MAIN);
1041 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1042 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1043 startActivity(intent);
1044 }
1045 finish();
1046 }
1047 public void onServiceDisconnected(ComponentName classname) {
1048 }
1049 };
1050
1051 private void setRepeatButtonImage() {
1052 try {
1053 switch (mService.getRepeatMode()) {
1054 case MediaPlaybackService.REPEAT_ALL:
1055 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1056 break;
1057 case MediaPlaybackService.REPEAT_CURRENT:
1058 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1059 break;
1060 default:
1061 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1062 break;
1063 }
1064 } catch (RemoteException ex) {
1065 }
1066 }
1067
1068 private void setShuffleButtonImage() {
1069 try {
1070 switch (mService.getShuffleMode()) {
1071 case MediaPlaybackService.SHUFFLE_NONE:
1072 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1073 break;
1074 case MediaPlaybackService.SHUFFLE_AUTO:
1075 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1076 break;
1077 default:
1078 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1079 break;
1080 }
1081 } catch (RemoteException ex) {
1082 }
1083 }
1084
1085 private void setPauseButtonImage() {
1086 try {
1087 if (mService != null && mService.isPlaying()) {
1088 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1089 } else {
1090 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1091 }
1092 } catch (RemoteException ex) {
1093 }
1094 }
1095
1096 private ImageView mAlbum;
1097 private TextView mCurrentTime;
1098 private TextView mTotalTime;
1099 private TextView mArtistName;
1100 private TextView mAlbumName;
1101 private TextView mTrackName;
1102 private ProgressBar mProgress;
1103 private long mPosOverride = -1;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -07001104 private boolean mFromTouch = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001105 private long mDuration;
1106 private int seekmethod;
1107 private boolean paused;
1108
1109 private static final int REFRESH = 1;
1110 private static final int QUIT = 2;
1111 private static final int GET_ALBUM_ART = 3;
1112 private static final int ALBUM_ART_DECODED = 4;
1113
1114 private void queueNextRefresh(long delay) {
1115 if (!paused) {
1116 Message msg = mHandler.obtainMessage(REFRESH);
1117 mHandler.removeMessages(REFRESH);
1118 mHandler.sendMessageDelayed(msg, delay);
1119 }
1120 }
1121
1122 private long refreshNow() {
1123 if(mService == null)
1124 return 500;
1125 try {
1126 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
1127 long remaining = 1000 - (pos % 1000);
1128 if ((pos >= 0) && (mDuration > 0)) {
1129 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
1130
1131 if (mService.isPlaying()) {
1132 mCurrentTime.setVisibility(View.VISIBLE);
1133 } else {
1134 // blink the counter
1135 int vis = mCurrentTime.getVisibility();
1136 mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
1137 remaining = 500;
1138 }
1139
1140 mProgress.setProgress((int) (1000 * pos / mDuration));
1141 } else {
1142 mCurrentTime.setText("--:--");
1143 mProgress.setProgress(1000);
1144 }
1145 // return the number of milliseconds until the next full second, so
1146 // the counter can be updated at just the right time
1147 return remaining;
1148 } catch (RemoteException ex) {
1149 }
1150 return 500;
1151 }
1152
1153 private final Handler mHandler = new Handler() {
1154 @Override
1155 public void handleMessage(Message msg) {
1156 switch (msg.what) {
1157 case ALBUM_ART_DECODED:
1158 mAlbum.setImageBitmap((Bitmap)msg.obj);
1159 mAlbum.getDrawable().setDither(true);
1160 break;
1161
1162 case REFRESH:
1163 long next = refreshNow();
1164 queueNextRefresh(next);
1165 break;
1166
1167 case QUIT:
1168 // This can be moved back to onCreate once the bug that prevents
1169 // Dialogs from being started from onCreate/onResume is fixed.
1170 new AlertDialog.Builder(MediaPlaybackActivity.this)
1171 .setTitle(R.string.service_start_error_title)
1172 .setMessage(R.string.service_start_error_msg)
1173 .setPositiveButton(R.string.service_start_error_button,
1174 new DialogInterface.OnClickListener() {
1175 public void onClick(DialogInterface dialog, int whichButton) {
1176 finish();
1177 }
1178 })
1179 .setCancelable(false)
1180 .show();
1181 break;
1182
1183 default:
1184 break;
1185 }
1186 }
1187 };
1188
1189 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1190 @Override
1191 public void onReceive(Context context, Intent intent) {
1192 String action = intent.getAction();
1193 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1194 // redraw the artist/title info and
1195 // set new max for progress bar
1196 updateTrackInfo();
1197 setPauseButtonImage();
1198 queueNextRefresh(1);
1199 } else if (action.equals(MediaPlaybackService.PLAYBACK_COMPLETE)) {
1200 if (mOneShot) {
1201 finish();
1202 } else {
1203 setPauseButtonImage();
1204 }
1205 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
1206 setPauseButtonImage();
1207 }
1208 }
1209 };
1210
1211 private void updateTrackInfo() {
1212 if (mService == null) {
1213 return;
1214 }
1215 try {
1216 String path = mService.getPath();
1217 if (path == null) {
1218 finish();
1219 return;
1220 }
1221
1222 if (mService.getAudioId() < 0 && path.toLowerCase().startsWith("http://")) {
1223 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1224 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1225 mAlbum.setVisibility(View.GONE);
1226 mTrackName.setText(path);
1227 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1228 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, -1, 0).sendToTarget();
1229 } else {
1230 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1231 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1232 String artistName = mService.getArtistName();
1233 if (MediaFile.UNKNOWN_STRING.equals(artistName)) {
1234 artistName = getString(R.string.unknown_artist_name);
1235 }
1236 mArtistName.setText(artistName);
1237 String albumName = mService.getAlbumName();
1238 int albumid = mService.getAlbumId();
1239 if (MediaFile.UNKNOWN_STRING.equals(albumName)) {
1240 albumName = getString(R.string.unknown_album_name);
1241 albumid = -1;
1242 }
1243 mAlbumName.setText(albumName);
1244 mTrackName.setText(mService.getTrackName());
1245 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
1246 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, albumid, 0).sendToTarget();
1247 mAlbum.setVisibility(View.VISIBLE);
1248 }
1249 mDuration = mService.duration();
1250 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1251 } catch (RemoteException ex) {
1252 finish();
1253 }
1254 }
1255
1256 public class AlbumArtHandler extends Handler {
1257 private int mAlbumId = -1;
1258
1259 public AlbumArtHandler(Looper looper) {
1260 super(looper);
1261 }
1262 public void handleMessage(Message msg)
1263 {
1264 int albumid = msg.arg1;
1265 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1266 // while decoding the new image, show the default album art
1267 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1268 mHandler.removeMessages(ALBUM_ART_DECODED);
1269 mHandler.sendMessageDelayed(numsg, 300);
1270 Bitmap bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, albumid);
1271 if (bm == null) {
1272 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, -1);
1273 albumid = -1;
1274 }
1275 if (bm != null) {
1276 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1277 mHandler.removeMessages(ALBUM_ART_DECODED);
1278 mHandler.sendMessage(numsg);
1279 }
1280 mAlbumId = albumid;
1281 }
1282 }
1283 }
1284
1285 private class Worker implements Runnable {
1286 private final Object mLock = new Object();
1287 private Looper mLooper;
1288
1289 /**
1290 * Creates a worker thread with the given name. The thread
1291 * then runs a {@link android.os.Looper}.
1292 * @param name A name for the new thread
1293 */
1294 Worker(String name) {
1295 Thread t = new Thread(null, this, name);
1296 t.setPriority(Thread.MIN_PRIORITY);
1297 t.start();
1298 synchronized (mLock) {
1299 while (mLooper == null) {
1300 try {
1301 mLock.wait();
1302 } catch (InterruptedException ex) {
1303 }
1304 }
1305 }
1306 }
1307
1308 public Looper getLooper() {
1309 return mLooper;
1310 }
1311
1312 public void run() {
1313 synchronized (mLock) {
1314 Looper.prepare();
1315 mLooper = Looper.myLooper();
1316 mLock.notifyAll();
1317 }
1318 Looper.loop();
1319 }
1320
1321 public void quit() {
1322 mLooper.quit();
1323 }
1324 }
1325}
1326