blob: 671c5b49983b4e50a6acd38b7d16544e69044ce8 [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
Marco Nelissenf33a5752010-01-27 15:35:43 -080019import com.android.music.MusicUtils.ServiceToken;
20
The Android Open Source Project792a2202009-03-03 19:32:30 -080021import android.app.Activity;
22import android.app.AlertDialog;
Marco Nelissen0657c9e2009-10-19 17:22:10 -070023import android.app.KeyguardManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080024import android.app.SearchManager;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
Marco Nelissenb7841ac2009-05-05 14:36:25 -070027import android.content.ContentResolver;
Marco Nelissenc5f5f132009-07-15 15:04:36 -070028import android.content.ContentUris;
The Android Open Source Project792a2202009-03-03 19:32:30 -080029import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.ServiceConnection;
Marco Nelissenf2ef3b52010-09-21 15:47:27 -070034import android.content.pm.ResolveInfo;
Marco Nelissenb7841ac2009-05-05 14:36:25 -070035import android.content.res.Configuration;
Marco Nelissenc5f5f132009-07-15 15:04:36 -070036import android.database.Cursor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.graphics.Bitmap;
Marco Nelissenf2ef3b52010-09-21 15:47:27 -070038import android.media.audiofx.AudioEffect;
The Android Open Source Project792a2202009-03-03 19:32:30 -080039import android.media.AudioManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080040import android.net.Uri;
41import android.os.Bundle;
The Android Open Source Project792a2202009-03-03 19:32:30 -080042import android.os.Handler;
43import android.os.IBinder;
44import android.os.Looper;
45import android.os.Message;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -080046import android.os.RemoteException;
The Android Open Source Project792a2202009-03-03 19:32:30 -080047import android.os.SystemClock;
48import android.provider.MediaStore;
49import android.text.Layout;
50import android.text.TextUtils.TruncateAt;
51import android.util.Log;
52import android.view.KeyEvent;
53import android.view.Menu;
54import android.view.MenuItem;
55import android.view.MotionEvent;
56import android.view.SubMenu;
57import android.view.View;
58import android.view.ViewConfiguration;
59import android.view.Window;
60import android.widget.ImageButton;
61import android.widget.ImageView;
62import android.widget.ProgressBar;
63import android.widget.SeekBar;
64import android.widget.TextView;
65import android.widget.Toast;
66import android.widget.SeekBar.OnSeekBarChangeListener;
67
Jack Heb0fba8b2017-01-26 15:54:38 -080068public class MediaPlaybackActivity extends Activity
69 implements MusicUtils.Defs, View.OnTouchListener, View.OnLongClickListener {
The Android Open Source Project792a2202009-03-03 19:32:30 -080070 private static final int USE_AS_RINGTONE = CHILD_MENU_BASE;
Marco Nelissen8d08ec22010-05-10 14:05:24 -070071
The Android Open Source Project792a2202009-03-03 19:32:30 -080072 private boolean mSeeking = false;
Marco Nelissencd582c62009-09-10 08:35:36 -070073 private boolean mDeviceHasDpad;
The Android Open Source Project792a2202009-03-03 19:32:30 -080074 private long mStartSeekPos = 0;
75 private long mLastSeekEventTime;
76 private IMediaPlaybackService mService = null;
77 private RepeatingImageButton mPrevButton;
78 private ImageButton mPauseButton;
79 private RepeatingImageButton mNextButton;
80 private ImageButton mRepeatButton;
81 private ImageButton mShuffleButton;
82 private ImageButton mQueueButton;
83 private Worker mAlbumArtWorker;
84 private AlbumArtHandler mAlbumArtHandler;
85 private Toast mToast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080086 private int mTouchSlop;
Marco Nelissenf33a5752010-01-27 15:35:43 -080087 private ServiceToken mToken;
The Android Open Source Project792a2202009-03-03 19:32:30 -080088
Jack Heb0fba8b2017-01-26 15:54:38 -080089 public MediaPlaybackActivity() {}
The Android Open Source Project792a2202009-03-03 19:32:30 -080090
91 /** Called when the activity is first created. */
92 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -080093 public void onCreate(Bundle icicle) {
The Android Open Source Project792a2202009-03-03 19:32:30 -080094 super.onCreate(icicle);
95 setVolumeControlStream(AudioManager.STREAM_MUSIC);
96
97 mAlbumArtWorker = new Worker("album art worker");
98 mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
99
100 requestWindowFeature(Window.FEATURE_NO_TITLE);
101 setContentView(R.layout.audio_player);
102
103 mCurrentTime = (TextView) findViewById(R.id.currenttime);
104 mTotalTime = (TextView) findViewById(R.id.totaltime);
105 mProgress = (ProgressBar) findViewById(android.R.id.progress);
106 mAlbum = (ImageView) findViewById(R.id.album);
107 mArtistName = (TextView) findViewById(R.id.artistname);
108 mAlbumName = (TextView) findViewById(R.id.albumname);
109 mTrackName = (TextView) findViewById(R.id.trackname);
110
Jack Heb0fba8b2017-01-26 15:54:38 -0800111 View v = (View) mArtistName.getParent();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800112 v.setOnTouchListener(this);
113 v.setOnLongClickListener(this);
114
Jack Heb0fba8b2017-01-26 15:54:38 -0800115 v = (View) mAlbumName.getParent();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800116 v.setOnTouchListener(this);
117 v.setOnLongClickListener(this);
118
Jack Heb0fba8b2017-01-26 15:54:38 -0800119 v = (View) mTrackName.getParent();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800120 v.setOnTouchListener(this);
121 v.setOnLongClickListener(this);
Jack Heb0fba8b2017-01-26 15:54:38 -0800122
The Android Open Source Project792a2202009-03-03 19:32:30 -0800123 mPrevButton = (RepeatingImageButton) findViewById(R.id.prev);
124 mPrevButton.setOnClickListener(mPrevListener);
125 mPrevButton.setRepeatListener(mRewListener, 260);
126 mPauseButton = (ImageButton) findViewById(R.id.pause);
127 mPauseButton.requestFocus();
128 mPauseButton.setOnClickListener(mPauseListener);
129 mNextButton = (RepeatingImageButton) findViewById(R.id.next);
130 mNextButton.setOnClickListener(mNextListener);
131 mNextButton.setRepeatListener(mFfwdListener, 260);
132 seekmethod = 1;
133
Jack Heb0fba8b2017-01-26 15:54:38 -0800134 mDeviceHasDpad =
135 (getResources().getConfiguration().navigation == Configuration.NAVIGATION_DPAD);
136
The Android Open Source Project792a2202009-03-03 19:32:30 -0800137 mQueueButton = (ImageButton) findViewById(R.id.curplaylist);
138 mQueueButton.setOnClickListener(mQueueListener);
139 mShuffleButton = ((ImageButton) findViewById(R.id.shuffle));
140 mShuffleButton.setOnClickListener(mShuffleListener);
141 mRepeatButton = ((ImageButton) findViewById(R.id.repeat));
142 mRepeatButton.setOnClickListener(mRepeatListener);
Jack Heb0fba8b2017-01-26 15:54:38 -0800143
The Android Open Source Project792a2202009-03-03 19:32:30 -0800144 if (mProgress instanceof SeekBar) {
145 SeekBar seeker = (SeekBar) mProgress;
146 seeker.setOnSeekBarChangeListener(mSeekListener);
147 }
148 mProgress.setMax(1000);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800149
150 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
151 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800152
The Android Open Source Project792a2202009-03-03 19:32:30 -0800153 int mInitialX = -1;
154 int mLastX = -1;
155 int mTextWidth = 0;
156 int mViewWidth = 0;
157 boolean mDraggingLabel = false;
Jack Heb0fba8b2017-01-26 15:54:38 -0800158
The Android Open Source Project792a2202009-03-03 19:32:30 -0800159 TextView textViewForContainer(View v) {
160 View vv = v.findViewById(R.id.artistname);
161 if (vv != null) return (TextView) vv;
162 vv = v.findViewById(R.id.albumname);
163 if (vv != null) return (TextView) vv;
164 vv = v.findViewById(R.id.trackname);
165 if (vv != null) return (TextView) vv;
166 return null;
167 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800168
The Android Open Source Project792a2202009-03-03 19:32:30 -0800169 public boolean onTouch(View v, MotionEvent event) {
170 int action = event.getAction();
171 TextView tv = textViewForContainer(v);
172 if (tv == null) {
173 return false;
174 }
175 if (action == MotionEvent.ACTION_DOWN) {
176 v.setBackgroundColor(0xff606060);
177 mInitialX = mLastX = (int) event.getX();
178 mDraggingLabel = false;
Jack Heb0fba8b2017-01-26 15:54:38 -0800179 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800180 v.setBackgroundColor(0);
181 if (mDraggingLabel) {
182 Message msg = mLabelScroller.obtainMessage(0, tv);
183 mLabelScroller.sendMessageDelayed(msg, 1000);
184 }
185 } else if (action == MotionEvent.ACTION_MOVE) {
186 if (mDraggingLabel) {
187 int scrollx = tv.getScrollX();
188 int x = (int) event.getX();
189 int delta = mLastX - x;
190 if (delta != 0) {
191 mLastX = x;
192 scrollx += delta;
193 if (scrollx > mTextWidth) {
194 // scrolled the text completely off the view to the left
195 scrollx -= mTextWidth;
196 scrollx -= mViewWidth;
197 }
198 if (scrollx < -mViewWidth) {
199 // scrolled the text completely off the view to the right
200 scrollx += mViewWidth;
201 scrollx += mTextWidth;
202 }
203 tv.scrollTo(scrollx, 0);
204 }
205 return true;
206 }
207 int delta = mInitialX - (int) event.getX();
208 if (Math.abs(delta) > mTouchSlop) {
209 // start moving
210 mLabelScroller.removeMessages(0, tv);
Jack Heb0fba8b2017-01-26 15:54:38 -0800211
The Android Open Source Project792a2202009-03-03 19:32:30 -0800212 // Only turn ellipsizing off when it's not already off, because it
213 // causes the scroll position to be reset to 0.
214 if (tv.getEllipsize() != null) {
215 tv.setEllipsize(null);
216 }
217 Layout ll = tv.getLayout();
218 // layout might be null if the text just changed, or ellipsizing
219 // was just turned off
220 if (ll == null) {
221 return false;
222 }
223 // get the non-ellipsized line width, to determine whether scrolling
224 // should even be allowed
225 mTextWidth = (int) tv.getLayout().getLineWidth(0);
226 mViewWidth = tv.getWidth();
227 if (mViewWidth > mTextWidth) {
228 tv.setEllipsize(TruncateAt.END);
229 v.cancelLongPress();
230 return false;
231 }
232 mDraggingLabel = true;
233 tv.setHorizontalFadingEdgeEnabled(true);
234 v.cancelLongPress();
235 return true;
236 }
237 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800238 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800239 }
240
241 Handler mLabelScroller = new Handler() {
242 @Override
243 public void handleMessage(Message msg) {
244 TextView tv = (TextView) msg.obj;
245 int x = tv.getScrollX();
246 x = x * 3 / 4;
247 tv.scrollTo(x, 0);
248 if (x == 0) {
249 tv.setEllipsize(TruncateAt.END);
250 } else {
251 Message newmsg = obtainMessage(0, tv);
252 mLabelScroller.sendMessageDelayed(newmsg, 15);
253 }
254 }
255 };
The Android Open Source Project792a2202009-03-03 19:32:30 -0800256
Jack Heb0fba8b2017-01-26 15:54:38 -0800257 public boolean onLongClick(View view) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800258 CharSequence title = null;
259 String mime = null;
260 String query = null;
261 String artist;
262 String album;
263 String song;
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700264 long audioid;
Jack Heb0fba8b2017-01-26 15:54:38 -0800265
The Android Open Source Project792a2202009-03-03 19:32:30 -0800266 try {
267 artist = mService.getArtistName();
268 album = mService.getAlbumName();
269 song = mService.getTrackName();
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700270 audioid = mService.getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800271 } catch (RemoteException ex) {
272 return true;
Marco Nelissenf7b53ab2009-10-13 14:54:25 -0700273 } catch (NullPointerException ex) {
274 // we might not actually have the service yet
275 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800276 }
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700277
Jack Heb0fba8b2017-01-26 15:54:38 -0800278 if (MediaStore.UNKNOWN_STRING.equals(album) && MediaStore.UNKNOWN_STRING.equals(artist)
279 && song != null && song.startsWith("recording")) {
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700280 // not music
281 return false;
282 }
Marco Nelissen9df6e732009-09-02 13:00:49 -0700283
284 if (audioid < 0) {
285 return false;
286 }
287
Marco Nelissenc5f5f132009-07-15 15:04:36 -0700288 Cursor c = MusicUtils.query(this,
289 ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioid),
290 new String[] {MediaStore.Audio.Media.IS_MUSIC}, null, null, null);
291 boolean ismusic = true;
292 if (c != null) {
293 if (c.moveToFirst()) {
294 ismusic = c.getInt(0) != 0;
295 }
296 c.close();
297 }
298 if (!ismusic) {
299 return false;
300 }
301
Jack Heb0fba8b2017-01-26 15:54:38 -0800302 boolean knownartist = (artist != null) && !MediaStore.UNKNOWN_STRING.equals(artist);
Andreas Huberc8398492009-04-13 16:15:57 -0700303
Jack Heb0fba8b2017-01-26 15:54:38 -0800304 boolean knownalbum = (album != null) && !MediaStore.UNKNOWN_STRING.equals(album);
305
The Android Open Source Project792a2202009-03-03 19:32:30 -0800306 if (knownartist && view.equals(mArtistName.getParent())) {
307 title = artist;
Marco Nelissen756c3f52009-05-14 10:07:23 -0700308 query = artist;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800309 mime = MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE;
310 } else if (knownalbum && view.equals(mAlbumName.getParent())) {
311 title = album;
312 if (knownartist) {
313 query = artist + " " + album;
314 } else {
315 query = album;
316 }
317 mime = MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE;
318 } else if (view.equals(mTrackName.getParent()) || !knownartist || !knownalbum) {
Marco Nelissenf4d4b342010-01-04 15:01:18 -0800319 if ((song == null) || MediaStore.UNKNOWN_STRING.equals(song)) {
Andreas Huberc8398492009-04-13 16:15:57 -0700320 // A popup of the form "Search for null/'' using ..." is pretty
321 // unhelpful, plus, we won't find any way to buy it anyway.
322 return true;
323 }
324
The Android Open Source Project792a2202009-03-03 19:32:30 -0800325 title = song;
326 if (knownartist) {
327 query = artist + " " + song;
328 } else {
329 query = song;
330 }
331 mime = "audio/*"; // the specific type doesn't matter, so don't bother retrieving it
332 } else {
333 throw new RuntimeException("shouldn't be here");
334 }
335 title = getString(R.string.mediasearch, title);
336
337 Intent i = new Intent();
338 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
339 i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
340 i.putExtra(SearchManager.QUERY, query);
Jack Heb0fba8b2017-01-26 15:54:38 -0800341 if (knownartist) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800342 i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
343 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800344 if (knownalbum) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800345 i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
346 }
347 i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, song);
348 i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, mime);
349
350 startActivity(Intent.createChooser(i, title));
351 return true;
352 }
353
354 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
355 public void onStartTrackingTouch(SeekBar bar) {
356 mLastSeekEventTime = 0;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700357 mFromTouch = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800358 }
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700359 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
360 if (!fromuser || (mService == null)) return;
361 long now = SystemClock.elapsedRealtime();
362 if ((now - mLastSeekEventTime) > 250) {
363 mLastSeekEventTime = now;
364 mPosOverride = mDuration * progress / 1000;
365 try {
366 mService.seek(mPosOverride);
367 } catch (RemoteException ex) {
368 }
369
370 // trackball event, allow progress updates
371 if (!mFromTouch) {
372 refreshNow();
373 mPosOverride = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800374 }
375 }
376 }
377 public void onStopTrackingTouch(SeekBar bar) {
378 mPosOverride = -1;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -0700379 mFromTouch = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800380 }
381 };
Jack Heb0fba8b2017-01-26 15:54:38 -0800382
The Android Open Source Project792a2202009-03-03 19:32:30 -0800383 private View.OnClickListener mQueueListener = new View.OnClickListener() {
384 public void onClick(View v) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800385 startActivity(new Intent(Intent.ACTION_EDIT)
386 .setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track")
387 .putExtra("playlist", "nowplaying"));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800388 }
389 };
Jack Heb0fba8b2017-01-26 15:54:38 -0800390
The Android Open Source Project792a2202009-03-03 19:32:30 -0800391 private View.OnClickListener mShuffleListener = new View.OnClickListener() {
392 public void onClick(View v) {
393 toggleShuffle();
394 }
395 };
396
397 private View.OnClickListener mRepeatListener = new View.OnClickListener() {
398 public void onClick(View v) {
399 cycleRepeat();
400 }
401 };
402
403 private View.OnClickListener mPauseListener = new View.OnClickListener() {
404 public void onClick(View v) {
405 doPauseResume();
406 }
407 };
408
409 private View.OnClickListener mPrevListener = new View.OnClickListener() {
410 public void onClick(View v) {
411 if (mService == null) return;
412 try {
413 if (mService.position() < 2000) {
414 mService.prev();
415 } else {
416 mService.seek(0);
417 mService.play();
418 }
419 } catch (RemoteException ex) {
420 }
421 }
422 };
423
424 private View.OnClickListener mNextListener = new View.OnClickListener() {
425 public void onClick(View v) {
426 if (mService == null) return;
427 try {
428 mService.next();
429 } catch (RemoteException ex) {
430 }
431 }
432 };
433
434 private RepeatingImageButton.RepeatListener mRewListener =
Jack Heb0fba8b2017-01-26 15:54:38 -0800435 new RepeatingImageButton.RepeatListener() {
436 public void onRepeat(View v, long howlong, int repcnt) {
437 scanBackward(repcnt, howlong);
438 }
439 };
440
The Android Open Source Project792a2202009-03-03 19:32:30 -0800441 private RepeatingImageButton.RepeatListener mFfwdListener =
Jack Heb0fba8b2017-01-26 15:54:38 -0800442 new RepeatingImageButton.RepeatListener() {
443 public void onRepeat(View v, long howlong, int repcnt) {
444 scanForward(repcnt, howlong);
445 }
446 };
447
The Android Open Source Project792a2202009-03-03 19:32:30 -0800448 @Override
449 public void onStop() {
450 paused = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800451 mHandler.removeMessages(REFRESH);
452 unregisterReceiver(mStatusListener);
Marco Nelissenf33a5752010-01-27 15:35:43 -0800453 MusicUtils.unbindFromService(mToken);
Marco Nelissen47b989b2009-05-27 12:57:06 -0700454 mService = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800455 super.onStop();
456 }
457
458 @Override
The Android Open Source Project792a2202009-03-03 19:32:30 -0800459 public void onStart() {
460 super.onStart();
461 paused = false;
462
Marco Nelissenf33a5752010-01-27 15:35:43 -0800463 mToken = MusicUtils.bindToService(this, osc);
464 if (mToken == null) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800465 // something went wrong
466 mHandler.sendEmptyMessage(QUIT);
467 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800468
The Android Open Source Project792a2202009-03-03 19:32:30 -0800469 IntentFilter f = new IntentFilter();
470 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
471 f.addAction(MediaPlaybackService.META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800472 registerReceiver(mStatusListener, new IntentFilter(f));
473 updateTrackInfo();
474 long next = refreshNow();
475 queueNextRefresh(next);
476 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800477
The Android Open Source Project792a2202009-03-03 19:32:30 -0800478 @Override
479 public void onNewIntent(Intent intent) {
480 setIntent(intent);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800481 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800482
The Android Open Source Project792a2202009-03-03 19:32:30 -0800483 @Override
484 public void onResume() {
485 super.onResume();
486 updateTrackInfo();
487 setPauseButtonImage();
488 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800489
The Android Open Source Project792a2202009-03-03 19:32:30 -0800490 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -0800491 public void onDestroy() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800492 mAlbumArtWorker.quit();
493 super.onDestroy();
Jack Heb0fba8b2017-01-26 15:54:38 -0800494 // System.out.println("***************** playback activity onDestroy\n");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800495 }
496
497 @Override
498 public boolean onCreateOptionsMenu(Menu menu) {
499 super.onCreateOptionsMenu(menu);
Marco Nelissen9d989c42009-05-13 15:31:23 -0700500 // Don't show the menu items if we got launched by path/filedescriptor, or
501 // if we're in one shot mode. In most cases, these menu items are not
502 // useful in those modes, so for consistency we never show them in these
503 // modes, instead of tailoring them to the specific file being played.
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700504 if (MusicUtils.getCurrentAudioId() >= 0) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800505 menu.add(0, GOTO_START, 0, R.string.goto_start)
506 .setIcon(R.drawable.ic_menu_music_library);
507 menu.add(0, PARTY_SHUFFLE, 0,
508 R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
509 SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist)
510 .setIcon(android.R.drawable.ic_menu_add);
Marco Nelissen0657c9e2009-10-19 17:22:10 -0700511 // these next two are in a separate group, so they can be shown/hidden as needed
512 // based on the keyguard state
513 menu.add(1, USE_AS_RINGTONE, 0, R.string.ringtone_menu_short)
514 .setIcon(R.drawable.ic_menu_set_as_ringtone);
Jack Heb0fba8b2017-01-26 15:54:38 -0800515 menu.add(1, DELETE_ITEM, 0, R.string.delete_item).setIcon(R.drawable.ic_menu_delete);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700516
517 Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
518 if (getPackageManager().resolveActivity(i, 0) != null) {
Marco Nelissen7c065602010-10-13 13:41:47 -0700519 menu.add(0, EFFECTS_PANEL, 0, R.string.effectspanel).setIcon(R.drawable.ic_menu_eq);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700520 }
521
Marco Nelissen9d989c42009-05-13 15:31:23 -0700522 return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800523 }
Marco Nelissen9d989c42009-05-13 15:31:23 -0700524 return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800525 }
526
527 @Override
528 public boolean onPrepareOptionsMenu(Menu menu) {
Gloria Wang21c22ae2010-04-01 11:10:58 -0700529 if (mService == null) return false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800530 MenuItem item = menu.findItem(PARTY_SHUFFLE);
531 if (item != null) {
532 int shuffle = MusicUtils.getCurrentShuffleMode();
533 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
534 item.setIcon(R.drawable.ic_menu_party_shuffle);
535 item.setTitle(R.string.party_shuffle_off);
536 } else {
537 item.setIcon(R.drawable.ic_menu_party_shuffle);
538 item.setTitle(R.string.party_shuffle);
539 }
540 }
Marco Nelissen64de08b2009-10-19 18:04:24 -0700541
Marco Nelissen1c0d6002009-10-12 09:24:12 -0700542 item = menu.findItem(ADD_TO_PLAYLIST);
543 if (item != null) {
544 SubMenu sub = item.getSubMenu();
545 MusicUtils.makePlaylistMenu(this, sub);
546 }
Marco Nelissen64de08b2009-10-19 18:04:24 -0700547
Marco Nelissen0657c9e2009-10-19 17:22:10 -0700548 KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
549 menu.setGroupVisible(1, !km.inKeyguardRestrictedInputMode());
Marco Nelissen64de08b2009-10-19 18:04:24 -0700550
The Android Open Source Project792a2202009-03-03 19:32:30 -0800551 return true;
552 }
553
554 @Override
555 public boolean onOptionsItemSelected(MenuItem item) {
556 Intent intent;
557 try {
558 switch (item.getItemId()) {
559 case GOTO_START:
560 intent = new Intent();
561 intent.setClass(this, MusicBrowserActivity.class);
Marco Nelissen2e8e5a22010-04-13 10:07:54 -0700562 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800563 startActivity(intent);
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800564 finish();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800565 break;
566 case USE_AS_RINGTONE: {
567 // Set the system setting to make this the current ringtone
568 if (mService != null) {
569 MusicUtils.setRingtone(this, mService.getAudioId());
570 }
571 return true;
572 }
573 case PARTY_SHUFFLE:
Marco Nelissenec0c57a2009-12-12 12:27:11 -0800574 MusicUtils.togglePartyShuffle();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800575 setShuffleButtonImage();
576 break;
Jack Heb0fba8b2017-01-26 15:54:38 -0800577
The Android Open Source Project792a2202009-03-03 19:32:30 -0800578 case NEW_PLAYLIST: {
579 intent = new Intent();
580 intent.setClass(this, CreatePlaylist.class);
581 startActivityForResult(intent, NEW_PLAYLIST);
582 return true;
583 }
584
585 case PLAYLIST_SELECTED: {
Jack Heb0fba8b2017-01-26 15:54:38 -0800586 long[] list = new long[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800587 list[0] = MusicUtils.getCurrentAudioId();
Marco Nelissenbd447b62009-06-29 14:52:05 -0700588 long playlist = item.getIntent().getLongExtra("playlist", 0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800589 MusicUtils.addToPlaylist(this, list, playlist);
590 return true;
591 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800592
The Android Open Source Project792a2202009-03-03 19:32:30 -0800593 case DELETE_ITEM: {
594 if (mService != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800595 long[] list = new long[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800596 list[0] = MusicUtils.getCurrentAudioId();
597 Bundle b = new Bundle();
Eric Fischerecf5b652010-10-18 14:20:30 -0700598 String f;
599 if (android.os.Environment.isExternalStorageRemovable()) {
600 f = getString(R.string.delete_song_desc, mService.getTrackName());
601 } else {
Jack Heb0fba8b2017-01-26 15:54:38 -0800602 f = getString(
603 R.string.delete_song_desc_nosdcard, mService.getTrackName());
Eric Fischerecf5b652010-10-18 14:20:30 -0700604 }
605 b.putString("description", f);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700606 b.putLongArray("items", list);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800607 intent = new Intent();
608 intent.setClass(this, DeleteItems.class);
609 intent.putExtras(b);
610 startActivityForResult(intent, -1);
611 }
612 return true;
613 }
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700614
615 case EFFECTS_PANEL: {
616 Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
617 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mService.getAudioSessionId());
618 startActivityForResult(i, EFFECTS_PANEL);
619 return true;
620 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800621 }
622 } catch (RemoteException ex) {
623 }
624 return super.onOptionsItemSelected(item);
625 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800626
The Android Open Source Project792a2202009-03-03 19:32:30 -0800627 @Override
628 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
629 if (resultCode != RESULT_OK) {
630 return;
631 }
632 switch (requestCode) {
633 case NEW_PLAYLIST:
634 Uri uri = intent.getData();
635 if (uri != null) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800636 long[] list = new long[1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800637 list[0] = MusicUtils.getCurrentAudioId();
638 int playlist = Integer.parseInt(uri.getLastPathSegment());
639 MusicUtils.addToPlaylist(this, list, playlist);
640 }
641 break;
642 }
643 }
644 private final int keyboard[][] = {
Jack Heb0fba8b2017-01-26 15:54:38 -0800645 {
646 KeyEvent.KEYCODE_Q, KeyEvent.KEYCODE_W, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_R,
647 KeyEvent.KEYCODE_T, KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_I,
648 KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_P,
649 },
650 {
651 KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_F,
652 KeyEvent.KEYCODE_G, KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_K,
653 KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_DEL,
654 },
655 {KeyEvent.KEYCODE_Z, KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_V,
656 KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_M,
657 KeyEvent.KEYCODE_COMMA, KeyEvent.KEYCODE_PERIOD, KeyEvent.KEYCODE_ENTER}
The Android Open Source Project792a2202009-03-03 19:32:30 -0800658
659 };
660
661 private int lastX;
662 private int lastY;
663
Jack Heb0fba8b2017-01-26 15:54:38 -0800664 private boolean seekMethod1(int keyCode) {
Marco Nelissenc5020e22009-10-12 15:59:52 -0700665 if (mService == null) return false;
Jack Heb0fba8b2017-01-26 15:54:38 -0800666 for (int x = 0; x < 10; x++) {
667 for (int y = 0; y < 3; y++) {
668 if (keyboard[y][x] == keyCode) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800669 int dir = 0;
670 // top row
Jack Heb0fba8b2017-01-26 15:54:38 -0800671 if (x == lastX && y == lastY)
672 dir = 0;
673 else if (y == 0 && lastY == 0 && x > lastX)
674 dir = 1;
675 else if (y == 0 && lastY == 0 && x < lastX)
676 dir = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800677 // bottom row
Jack Heb0fba8b2017-01-26 15:54:38 -0800678 else if (y == 2 && lastY == 2 && x > lastX)
679 dir = -1;
680 else if (y == 2 && lastY == 2 && x < lastX)
681 dir = 1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800682 // moving up
Jack Heb0fba8b2017-01-26 15:54:38 -0800683 else if (y < lastY && x <= 4)
684 dir = 1;
685 else if (y < lastY && x >= 5)
686 dir = -1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800687 // moving down
Jack Heb0fba8b2017-01-26 15:54:38 -0800688 else if (y > lastY && x <= 4)
689 dir = -1;
690 else if (y > lastY && x >= 5)
691 dir = 1;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800692 lastX = x;
693 lastY = y;
694 try {
695 mService.seek(mService.position() + dir * 5);
696 } catch (RemoteException ex) {
697 }
698 refreshNow();
699 return true;
700 }
701 }
702 }
703 lastX = -1;
704 lastY = -1;
705 return false;
706 }
707
Jack Heb0fba8b2017-01-26 15:54:38 -0800708 private boolean seekMethod2(int keyCode) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800709 if (mService == null) return false;
Jack Heb0fba8b2017-01-26 15:54:38 -0800710 for (int i = 0; i < 10; i++) {
711 if (keyboard[0][i] == keyCode) {
712 int seekpercentage = 100 * i / 10;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800713 try {
714 mService.seek(mService.duration() * seekpercentage / 100);
715 } catch (RemoteException ex) {
716 }
717 refreshNow();
718 return true;
719 }
720 }
721 return false;
722 }
723
724 @Override
725 public boolean onKeyUp(int keyCode, KeyEvent event) {
726 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800727 switch (keyCode) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800728 case KeyEvent.KEYCODE_DPAD_LEFT:
Marco Nelissencd582c62009-09-10 08:35:36 -0700729 if (!useDpadMusicControl()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800730 break;
731 }
732 if (mService != null) {
733 if (!mSeeking && mStartSeekPos >= 0) {
734 mPauseButton.requestFocus();
735 if (mStartSeekPos < 1000) {
736 mService.prev();
737 } else {
738 mService.seek(0);
739 }
740 } else {
741 scanBackward(-1, event.getEventTime() - event.getDownTime());
742 mPauseButton.requestFocus();
743 mStartSeekPos = -1;
744 }
745 }
746 mSeeking = false;
747 mPosOverride = -1;
748 return true;
749 case KeyEvent.KEYCODE_DPAD_RIGHT:
Marco Nelissencd582c62009-09-10 08:35:36 -0700750 if (!useDpadMusicControl()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800751 break;
752 }
753 if (mService != null) {
754 if (!mSeeking && mStartSeekPos >= 0) {
755 mPauseButton.requestFocus();
756 mService.next();
757 } else {
758 scanForward(-1, event.getEventTime() - event.getDownTime());
759 mPauseButton.requestFocus();
760 mStartSeekPos = -1;
761 }
762 }
763 mSeeking = false;
764 mPosOverride = -1;
765 return true;
766 }
767 } catch (RemoteException ex) {
768 }
769 return super.onKeyUp(keyCode, event);
770 }
771
Marco Nelissencd582c62009-09-10 08:35:36 -0700772 private boolean useDpadMusicControl() {
Jack Heb0fba8b2017-01-26 15:54:38 -0800773 if (mDeviceHasDpad && (mPrevButton.isFocused() || mNextButton.isFocused()
774 || mPauseButton.isFocused())) {
Marco Nelissencd582c62009-09-10 08:35:36 -0700775 return true;
776 }
777 return false;
778 }
779
The Android Open Source Project792a2202009-03-03 19:32:30 -0800780 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -0800781 public boolean onKeyDown(int keyCode, KeyEvent event) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800782 int direction = -1;
783 int repcnt = event.getRepeatCount();
784
Jack Heb0fba8b2017-01-26 15:54:38 -0800785 if ((seekmethod == 0) ? seekMethod1(keyCode) : seekMethod2(keyCode)) return true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800786
Jack Heb0fba8b2017-01-26 15:54:38 -0800787 switch (keyCode) {
788 /*
789 // image scale
790 case KeyEvent.KEYCODE_Q: av.adjustParams(-0.05, 0.0, 0.0, 0.0, 0.0,-1.0);
791 break;
792 case KeyEvent.KEYCODE_E: av.adjustParams( 0.05, 0.0, 0.0, 0.0, 0.0, 1.0);
793 break;
794 // image translate
795 case KeyEvent.KEYCODE_W: av.adjustParams( 0.0, 0.0,-1.0, 0.0, 0.0, 0.0);
796 break;
797 case KeyEvent.KEYCODE_X: av.adjustParams( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
798 break;
799 case KeyEvent.KEYCODE_A: av.adjustParams( 0.0,-1.0, 0.0, 0.0, 0.0, 0.0);
800 break;
801 case KeyEvent.KEYCODE_D: av.adjustParams( 0.0, 1.0, 0.0, 0.0, 0.0, 0.0);
802 break;
803 // camera rotation
804 case KeyEvent.KEYCODE_R: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0,-1.0);
805 break;
806 case KeyEvent.KEYCODE_U: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
807 break;
808 // camera translate
809 case KeyEvent.KEYCODE_Y: av.adjustParams( 0.0, 0.0, 0.0, 0.0,-1.0, 0.0);
810 break;
811 case KeyEvent.KEYCODE_N: av.adjustParams( 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
812 break;
813 case KeyEvent.KEYCODE_G: av.adjustParams( 0.0, 0.0, 0.0,-1.0, 0.0, 0.0);
814 break;
815 case KeyEvent.KEYCODE_J: av.adjustParams( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
816 break;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800817
Jack Heb0fba8b2017-01-26 15:54:38 -0800818 */
The Android Open Source Project792a2202009-03-03 19:32:30 -0800819
820 case KeyEvent.KEYCODE_SLASH:
821 seekmethod = 1 - seekmethod;
822 return true;
823
824 case KeyEvent.KEYCODE_DPAD_LEFT:
Marco Nelissencd582c62009-09-10 08:35:36 -0700825 if (!useDpadMusicControl()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800826 break;
827 }
828 if (!mPrevButton.hasFocus()) {
829 mPrevButton.requestFocus();
830 }
831 scanBackward(repcnt, event.getEventTime() - event.getDownTime());
832 return true;
833 case KeyEvent.KEYCODE_DPAD_RIGHT:
Marco Nelissencd582c62009-09-10 08:35:36 -0700834 if (!useDpadMusicControl()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800835 break;
836 }
837 if (!mNextButton.hasFocus()) {
838 mNextButton.requestFocus();
839 }
840 scanForward(repcnt, event.getEventTime() - event.getDownTime());
841 return true;
842
843 case KeyEvent.KEYCODE_S:
844 toggleShuffle();
845 return true;
846
847 case KeyEvent.KEYCODE_DPAD_CENTER:
848 case KeyEvent.KEYCODE_SPACE:
849 doPauseResume();
850 return true;
851 }
852 return super.onKeyDown(keyCode, event);
853 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800854
The Android Open Source Project792a2202009-03-03 19:32:30 -0800855 private void scanBackward(int repcnt, long delta) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800856 if (mService == null) return;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800857 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800858 if (repcnt == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800859 mStartSeekPos = mService.position();
860 mLastSeekEventTime = 0;
861 mSeeking = false;
862 } else {
863 mSeeking = true;
864 if (delta < 5000) {
865 // seek at 10x speed for the first 5 seconds
Jack Heb0fba8b2017-01-26 15:54:38 -0800866 delta = delta * 10;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800867 } else {
868 // seek at 40x after that
869 delta = 50000 + (delta - 5000) * 40;
870 }
871 long newpos = mStartSeekPos - delta;
872 if (newpos < 0) {
873 // move to previous track
874 mService.prev();
875 long duration = mService.duration();
876 mStartSeekPos += duration;
877 newpos += duration;
878 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800879 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800880 mService.seek(newpos);
881 mLastSeekEventTime = delta;
882 }
883 if (repcnt >= 0) {
884 mPosOverride = newpos;
885 } else {
886 mPosOverride = -1;
887 }
888 refreshNow();
889 }
890 } catch (RemoteException ex) {
891 }
892 }
893
894 private void scanForward(int repcnt, long delta) {
Jack Heb0fba8b2017-01-26 15:54:38 -0800895 if (mService == null) return;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800896 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800897 if (repcnt == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800898 mStartSeekPos = mService.position();
899 mLastSeekEventTime = 0;
900 mSeeking = false;
901 } else {
902 mSeeking = true;
903 if (delta < 5000) {
904 // seek at 10x speed for the first 5 seconds
Jack Heb0fba8b2017-01-26 15:54:38 -0800905 delta = delta * 10;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800906 } else {
907 // seek at 40x after that
908 delta = 50000 + (delta - 5000) * 40;
909 }
910 long newpos = mStartSeekPos + delta;
911 long duration = mService.duration();
912 if (newpos >= duration) {
913 // move to next track
914 mService.next();
915 mStartSeekPos -= duration; // is OK to go negative
916 newpos -= duration;
917 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800918 if (((delta - mLastSeekEventTime) > 250) || repcnt < 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800919 mService.seek(newpos);
920 mLastSeekEventTime = delta;
921 }
922 if (repcnt >= 0) {
923 mPosOverride = newpos;
924 } else {
925 mPosOverride = -1;
926 }
927 refreshNow();
928 }
929 } catch (RemoteException ex) {
930 }
931 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800932
The Android Open Source Project792a2202009-03-03 19:32:30 -0800933 private void doPauseResume() {
934 try {
Jack Heb0fba8b2017-01-26 15:54:38 -0800935 if (mService != null) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800936 if (mService.isPlaying()) {
937 mService.pause();
938 } else {
939 mService.play();
940 }
941 refreshNow();
942 setPauseButtonImage();
943 }
944 } catch (RemoteException ex) {
945 }
946 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800947
The Android Open Source Project792a2202009-03-03 19:32:30 -0800948 private void toggleShuffle() {
949 if (mService == null) {
950 return;
951 }
952 try {
953 int shuffle = mService.getShuffleMode();
954 if (shuffle == MediaPlaybackService.SHUFFLE_NONE) {
955 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
956 if (mService.getRepeatMode() == MediaPlaybackService.REPEAT_CURRENT) {
957 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
958 setRepeatButtonImage();
959 }
960 showToast(R.string.shuffle_on_notif);
Jack Heb0fba8b2017-01-26 15:54:38 -0800961 } else if (shuffle == MediaPlaybackService.SHUFFLE_NORMAL
962 || shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800963 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
964 showToast(R.string.shuffle_off_notif);
965 } else {
966 Log.e("MediaPlaybackActivity", "Invalid shuffle mode: " + shuffle);
967 }
968 setShuffleButtonImage();
969 } catch (RemoteException ex) {
970 }
971 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800972
The Android Open Source Project792a2202009-03-03 19:32:30 -0800973 private void cycleRepeat() {
974 if (mService == null) {
975 return;
976 }
977 try {
978 int mode = mService.getRepeatMode();
979 if (mode == MediaPlaybackService.REPEAT_NONE) {
980 mService.setRepeatMode(MediaPlaybackService.REPEAT_ALL);
981 showToast(R.string.repeat_all_notif);
982 } else if (mode == MediaPlaybackService.REPEAT_ALL) {
983 mService.setRepeatMode(MediaPlaybackService.REPEAT_CURRENT);
984 if (mService.getShuffleMode() != MediaPlaybackService.SHUFFLE_NONE) {
985 mService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
986 setShuffleButtonImage();
987 }
988 showToast(R.string.repeat_current_notif);
989 } else {
990 mService.setRepeatMode(MediaPlaybackService.REPEAT_NONE);
991 showToast(R.string.repeat_off_notif);
992 }
993 setRepeatButtonImage();
994 } catch (RemoteException ex) {
995 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800996 }
Jack Heb0fba8b2017-01-26 15:54:38 -0800997
The Android Open Source Project792a2202009-03-03 19:32:30 -0800998 private void showToast(int resid) {
999 if (mToast == null) {
1000 mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
1001 }
1002 mToast.setText(resid);
1003 mToast.show();
1004 }
1005
1006 private void startPlayback() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001007 if (mService == null) return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001008 Intent intent = getIntent();
1009 String filename = "";
1010 Uri uri = intent.getData();
1011 if (uri != null && uri.toString().length() > 0) {
1012 // If this is a file:// URI, just use the path directly instead
1013 // of going through the open-from-filedescriptor codepath.
1014 String scheme = uri.getScheme();
1015 if ("file".equals(scheme)) {
1016 filename = uri.getPath();
1017 } else {
1018 filename = uri.toString();
1019 }
1020 try {
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001021 mService.stop();
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001022 mService.openFile(filename);
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001023 mService.play();
1024 setIntent(new Intent());
The Android Open Source Project792a2202009-03-03 19:32:30 -08001025 } catch (Exception ex) {
1026 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
1027 }
1028 }
1029
1030 updateTrackInfo();
1031 long next = refreshNow();
1032 queueNextRefresh(next);
1033 }
1034
1035 private ServiceConnection osc = new ServiceConnection() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001036 public void onServiceConnected(ComponentName classname, IBinder obj) {
1037 mService = IMediaPlaybackService.Stub.asInterface(obj);
1038 startPlayback();
1039 try {
1040 // Assume something is playing when the service says it is,
1041 // but also if the audio ID is valid but the service is paused.
1042 if (mService.getAudioId() >= 0 || mService.isPlaying()
1043 || mService.getPath() != null) {
1044 // something is playing now, we're done
1045 mRepeatButton.setVisibility(View.VISIBLE);
1046 mShuffleButton.setVisibility(View.VISIBLE);
1047 mQueueButton.setVisibility(View.VISIBLE);
1048 setRepeatButtonImage();
1049 setShuffleButtonImage();
1050 setPauseButtonImage();
1051 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001052 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001053 } catch (RemoteException ex) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001054 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001055 // Service is dead or not playing anything. If we got here as part
1056 // of a "play this file" Intent, exit. Otherwise go to the Music
1057 // app start screen.
1058 if (getIntent().getData() == null) {
1059 Intent intent = new Intent(Intent.ACTION_MAIN);
1060 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1061 intent.setClass(MediaPlaybackActivity.this, MusicBrowserActivity.class);
1062 startActivity(intent);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001063 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001064 finish();
1065 }
1066 public void onServiceDisconnected(ComponentName classname) {
1067 mService = null;
1068 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001069 };
1070
1071 private void setRepeatButtonImage() {
Gloria Wang21c22ae2010-04-01 11:10:58 -07001072 if (mService == null) return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001073 try {
1074 switch (mService.getRepeatMode()) {
1075 case MediaPlaybackService.REPEAT_ALL:
1076 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_all_btn);
1077 break;
1078 case MediaPlaybackService.REPEAT_CURRENT:
1079 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_once_btn);
1080 break;
1081 default:
1082 mRepeatButton.setImageResource(R.drawable.ic_mp_repeat_off_btn);
1083 break;
1084 }
1085 } catch (RemoteException ex) {
1086 }
1087 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001088
The Android Open Source Project792a2202009-03-03 19:32:30 -08001089 private void setShuffleButtonImage() {
Gloria Wang21c22ae2010-04-01 11:10:58 -07001090 if (mService == null) return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001091 try {
1092 switch (mService.getShuffleMode()) {
1093 case MediaPlaybackService.SHUFFLE_NONE:
1094 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_off_btn);
1095 break;
1096 case MediaPlaybackService.SHUFFLE_AUTO:
1097 mShuffleButton.setImageResource(R.drawable.ic_mp_partyshuffle_on_btn);
1098 break;
1099 default:
1100 mShuffleButton.setImageResource(R.drawable.ic_mp_shuffle_on_btn);
1101 break;
1102 }
1103 } catch (RemoteException ex) {
1104 }
1105 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001106
The Android Open Source Project792a2202009-03-03 19:32:30 -08001107 private void setPauseButtonImage() {
1108 try {
1109 if (mService != null && mService.isPlaying()) {
1110 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
1111 } else {
1112 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
1113 }
1114 } catch (RemoteException ex) {
1115 }
1116 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001117
The Android Open Source Project792a2202009-03-03 19:32:30 -08001118 private ImageView mAlbum;
1119 private TextView mCurrentTime;
1120 private TextView mTotalTime;
1121 private TextView mArtistName;
1122 private TextView mAlbumName;
1123 private TextView mTrackName;
1124 private ProgressBar mProgress;
1125 private long mPosOverride = -1;
The Android Open Source Projectc85f6772009-03-18 17:39:48 -07001126 private boolean mFromTouch = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001127 private long mDuration;
1128 private int seekmethod;
1129 private boolean paused;
1130
1131 private static final int REFRESH = 1;
1132 private static final int QUIT = 2;
1133 private static final int GET_ALBUM_ART = 3;
1134 private static final int ALBUM_ART_DECODED = 4;
1135
1136 private void queueNextRefresh(long delay) {
1137 if (!paused) {
1138 Message msg = mHandler.obtainMessage(REFRESH);
1139 mHandler.removeMessages(REFRESH);
1140 mHandler.sendMessageDelayed(msg, delay);
1141 }
1142 }
1143
1144 private long refreshNow() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001145 if (mService == null) return 500;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001146 try {
1147 long pos = mPosOverride < 0 ? mService.position() : mPosOverride;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001148 if ((pos >= 0) && (mDuration > 0)) {
1149 mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
Marco Nelissen38dc1232012-03-09 08:55:10 -08001150 int progress = (int) (1000 * pos / mDuration);
1151 mProgress.setProgress(progress);
Jack Heb0fba8b2017-01-26 15:54:38 -08001152
The Android Open Source Project792a2202009-03-03 19:32:30 -08001153 if (mService.isPlaying()) {
1154 mCurrentTime.setVisibility(View.VISIBLE);
1155 } else {
1156 // blink the counter
1157 int vis = mCurrentTime.getVisibility();
Jack Heb0fba8b2017-01-26 15:54:38 -08001158 mCurrentTime.setVisibility(
1159 vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE);
Marco Nelissen38dc1232012-03-09 08:55:10 -08001160 return 500;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001161 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001162 } else {
1163 mCurrentTime.setText("--:--");
1164 mProgress.setProgress(1000);
1165 }
Marco Nelissen38dc1232012-03-09 08:55:10 -08001166 // calculate the number of milliseconds until the next full second, so
The Android Open Source Project792a2202009-03-03 19:32:30 -08001167 // the counter can be updated at just the right time
Marco Nelissen38dc1232012-03-09 08:55:10 -08001168 long remaining = 1000 - (pos % 1000);
1169
1170 // approximate how often we would need to refresh the slider to
1171 // move it smoothly
1172 int width = mProgress.getWidth();
1173 if (width == 0) width = 320;
1174 long smoothrefreshtime = mDuration / width;
1175
1176 if (smoothrefreshtime > remaining) return remaining;
1177 if (smoothrefreshtime < 20) return 20;
1178 return smoothrefreshtime;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001179 } catch (RemoteException ex) {
1180 }
1181 return 500;
1182 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001183
The Android Open Source Project792a2202009-03-03 19:32:30 -08001184 private final Handler mHandler = new Handler() {
1185 @Override
1186 public void handleMessage(Message msg) {
1187 switch (msg.what) {
1188 case ALBUM_ART_DECODED:
Jack Heb0fba8b2017-01-26 15:54:38 -08001189 mAlbum.setImageBitmap((Bitmap) msg.obj);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001190 mAlbum.getDrawable().setDither(true);
1191 break;
1192
1193 case REFRESH:
1194 long next = refreshNow();
1195 queueNextRefresh(next);
1196 break;
Jack Heb0fba8b2017-01-26 15:54:38 -08001197
The Android Open Source Project792a2202009-03-03 19:32:30 -08001198 case QUIT:
1199 // This can be moved back to onCreate once the bug that prevents
1200 // Dialogs from being started from onCreate/onResume is fixed.
1201 new AlertDialog.Builder(MediaPlaybackActivity.this)
1202 .setTitle(R.string.service_start_error_title)
1203 .setMessage(R.string.service_start_error_msg)
1204 .setPositiveButton(R.string.service_start_error_button,
1205 new DialogInterface.OnClickListener() {
Jack Heb0fba8b2017-01-26 15:54:38 -08001206 public void onClick(
1207 DialogInterface dialog, int whichButton) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001208 finish();
1209 }
1210 })
1211 .setCancelable(false)
1212 .show();
1213 break;
1214
1215 default:
1216 break;
1217 }
1218 }
1219 };
1220
1221 private BroadcastReceiver mStatusListener = new BroadcastReceiver() {
1222 @Override
1223 public void onReceive(Context context, Intent intent) {
1224 String action = intent.getAction();
1225 if (action.equals(MediaPlaybackService.META_CHANGED)) {
1226 // redraw the artist/title info and
1227 // set new max for progress bar
1228 updateTrackInfo();
1229 setPauseButtonImage();
1230 queueNextRefresh(1);
Marco Nelissen6b507de2010-10-20 14:08:10 -07001231 } else if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001232 setPauseButtonImage();
1233 }
1234 }
1235 };
1236
Marco Nelissenbd447b62009-06-29 14:52:05 -07001237 private static class AlbumSongIdWrapper {
1238 public long albumid;
1239 public long songid;
1240 AlbumSongIdWrapper(long aid, long sid) {
1241 albumid = aid;
1242 songid = sid;
1243 }
1244 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001245
The Android Open Source Project792a2202009-03-03 19:32:30 -08001246 private void updateTrackInfo() {
1247 if (mService == null) {
1248 return;
1249 }
1250 try {
1251 String path = mService.getPath();
1252 if (path == null) {
1253 finish();
1254 return;
1255 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001256
1257 long songid = mService.getAudioId();
Marco Nelissen7a16cc72009-06-16 08:54:17 -07001258 if (songid < 0 && path.toLowerCase().startsWith("http://")) {
1259 // Once we can get album art and meta data from MediaPlayer, we
1260 // can show that info again when streaming.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001261 ((View) mArtistName.getParent()).setVisibility(View.INVISIBLE);
1262 ((View) mAlbumName.getParent()).setVisibility(View.INVISIBLE);
1263 mAlbum.setVisibility(View.GONE);
1264 mTrackName.setText(path);
1265 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
Jack Heb0fba8b2017-01-26 15:54:38 -08001266 mAlbumArtHandler.obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(-1, -1))
1267 .sendToTarget();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001268 } else {
1269 ((View) mArtistName.getParent()).setVisibility(View.VISIBLE);
1270 ((View) mAlbumName.getParent()).setVisibility(View.VISIBLE);
1271 String artistName = mService.getArtistName();
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001272 if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001273 artistName = getString(R.string.unknown_artist_name);
1274 }
1275 mArtistName.setText(artistName);
1276 String albumName = mService.getAlbumName();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001277 long albumid = mService.getAlbumId();
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001278 if (MediaStore.UNKNOWN_STRING.equals(albumName)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001279 albumName = getString(R.string.unknown_album_name);
1280 albumid = -1;
1281 }
1282 mAlbumName.setText(albumName);
1283 mTrackName.setText(mService.getTrackName());
1284 mAlbumArtHandler.removeMessages(GET_ALBUM_ART);
Jack Heb0fba8b2017-01-26 15:54:38 -08001285 mAlbumArtHandler
1286 .obtainMessage(GET_ALBUM_ART, new AlbumSongIdWrapper(albumid, songid))
1287 .sendToTarget();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001288 mAlbum.setVisibility(View.VISIBLE);
1289 }
1290 mDuration = mService.duration();
1291 mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000));
1292 } catch (RemoteException ex) {
1293 finish();
1294 }
1295 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001296
The Android Open Source Project792a2202009-03-03 19:32:30 -08001297 public class AlbumArtHandler extends Handler {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001298 private long mAlbumId = -1;
Jack Heb0fba8b2017-01-26 15:54:38 -08001299
The Android Open Source Project792a2202009-03-03 19:32:30 -08001300 public AlbumArtHandler(Looper looper) {
1301 super(looper);
1302 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001303 @Override
Jack Heb0fba8b2017-01-26 15:54:38 -08001304 public void handleMessage(Message msg) {
Marco Nelissenbd447b62009-06-29 14:52:05 -07001305 long albumid = ((AlbumSongIdWrapper) msg.obj).albumid;
1306 long songid = ((AlbumSongIdWrapper) msg.obj).songid;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001307 if (msg.what == GET_ALBUM_ART && (mAlbumId != albumid || albumid < 0)) {
1308 // while decoding the new image, show the default album art
1309 Message numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, null);
1310 mHandler.removeMessages(ALBUM_ART_DECODED);
1311 mHandler.sendMessageDelayed(numsg, 300);
Marco Nelissen05f5bd52010-08-24 16:51:39 -07001312 // Don't allow default artwork here, because we want to fall back to song-specific
1313 // album art if we can't find anything for the album.
Jack Heb0fba8b2017-01-26 15:54:38 -08001314 Bitmap bm =
1315 MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, albumid, false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001316 if (bm == null) {
Marco Nelissen7a16cc72009-06-16 08:54:17 -07001317 bm = MusicUtils.getArtwork(MediaPlaybackActivity.this, songid, -1);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001318 albumid = -1;
1319 }
1320 if (bm != null) {
1321 numsg = mHandler.obtainMessage(ALBUM_ART_DECODED, bm);
1322 mHandler.removeMessages(ALBUM_ART_DECODED);
1323 mHandler.sendMessage(numsg);
1324 }
1325 mAlbumId = albumid;
1326 }
1327 }
1328 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001329
Marco Nelissen756c3f52009-05-14 10:07:23 -07001330 private static class Worker implements Runnable {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001331 private final Object mLock = new Object();
1332 private Looper mLooper;
Jack Heb0fba8b2017-01-26 15:54:38 -08001333
The Android Open Source Project792a2202009-03-03 19:32:30 -08001334 /**
1335 * Creates a worker thread with the given name. The thread
1336 * then runs a {@link android.os.Looper}.
1337 * @param name A name for the new thread
1338 */
1339 Worker(String name) {
1340 Thread t = new Thread(null, this, name);
1341 t.setPriority(Thread.MIN_PRIORITY);
1342 t.start();
1343 synchronized (mLock) {
1344 while (mLooper == null) {
1345 try {
1346 mLock.wait();
1347 } catch (InterruptedException ex) {
1348 }
1349 }
1350 }
1351 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001352
The Android Open Source Project792a2202009-03-03 19:32:30 -08001353 public Looper getLooper() {
1354 return mLooper;
1355 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001356
The Android Open Source Project792a2202009-03-03 19:32:30 -08001357 public void run() {
1358 synchronized (mLock) {
1359 Looper.prepare();
1360 mLooper = Looper.myLooper();
1361 mLock.notifyAll();
1362 }
1363 Looper.loop();
1364 }
Jack Heb0fba8b2017-01-26 15:54:38 -08001365
The Android Open Source Project792a2202009-03-03 19:32:30 -08001366 public void quit() {
1367 mLooper.quit();
1368 }
1369 }
1370}