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