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