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