OSDN Git Service

AudioPreviewActivity: Better external audio previewing
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / ui / activities / preview / AudioPreviewActivity.java
1 /*
2 * Copyright (C) 2015 The CyanogenMod 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
17 package com.cyanogenmod.eleven.ui.activities.preview;
18
19 import android.app.Activity;
20 import android.content.AsyncQueryHandler;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ActivityInfo;
26 import android.database.Cursor;
27 import android.graphics.Rect;
28 import android.media.AudioManager;
29 import android.media.AudioManager.OnAudioFocusChangeListener;
30 import android.media.MediaPlayer;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.provider.MediaStore.Audio.Media;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.MotionEvent;
40 import android.view.View;
41 import android.view.View.OnClickListener;
42 import android.view.View.OnTouchListener;
43 import android.view.Window;
44 import android.widget.ImageButton;
45 import android.widget.ProgressBar;
46 import android.widget.SeekBar;
47 import android.widget.SeekBar.OnSeekBarChangeListener;
48 import android.widget.TextView;
49 import android.widget.Toast;
50 import com.cyanogenmod.eleven.R;
51 import com.cyanogenmod.eleven.ui.activities.preview.util.Logger;
52
53 import java.io.IOException;
54 import java.lang.ref.WeakReference;
55
56 /**
57  * AudioPreview
58  * <pre>
59  *     Preview plays external audio files in a dialog over the application
60  * </pre>
61  *
62  * @see {@link Activity}
63  * @see {@link android.media.MediaPlayer.OnCompletionListener}
64  * @see {@link android.media.MediaPlayer.OnErrorListener}
65  * @see {@link android.media.MediaPlayer.OnPreparedListener}
66  * @see {@link OnClickListener}
67  * @see {@link OnAudioFocusChangeListener}
68  * @see {@link OnSeekBarChangeListener}
69  */
70 public class AudioPreviewActivity extends Activity implements MediaPlayer.OnCompletionListener,
71         MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener, OnClickListener,
72         OnAudioFocusChangeListener, OnSeekBarChangeListener, OnTouchListener {
73
74     // Constants
75     private static final String TAG = AudioPreviewActivity.class.getSimpleName();
76     private static final int PROGRESS_DELAY_INTERVAL = 250;
77     private static final String SCHEME_CONTENT = "content";
78     private static final String SCHEME_FILE = "file";
79     private static final String SCHEME_HTTP = "http";
80     private static final String AUTHORITY_MEDIA = "media";
81     private static final int CONTENT_QUERY_TOKEN = 1000;
82     private static final int CONTENT_BAD_QUERY_TOKEN = CONTENT_QUERY_TOKEN + 1;
83     private static final String[] MEDIA_PROJECTION = new String[] {
84             Media.TITLE,
85             Media.ARTIST
86     };
87
88     // Seeking flag
89     private boolean mIsSeeking = false;
90     private boolean mWasPlaying = false;
91
92     @Override
93     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
94         if (mPreviewPlayer != null && mIsSeeking) {
95             mPreviewPlayer.seekTo(progress);
96         }
97     }
98
99     @Override
100     public void onStartTrackingTouch(SeekBar seekBar) {
101         mIsSeeking = true;
102         if (mCurrentState == State.PLAYING) {
103             mWasPlaying = true;
104             pausePlayback(false);
105         }
106     }
107
108     @Override
109     public void onStopTrackingTouch(SeekBar seekBar) {
110         if (mWasPlaying) {
111             startPlayback();
112         }
113         mWasPlaying = false;
114         mIsSeeking = false;
115     }
116
117     private enum State {
118         INIT,
119         PREPARED,
120         PLAYING,
121         PAUSED
122     }
123
124     /**
125      * <pre>
126      *     Handle some ui events
127      * </pre>
128      *
129      * @see {@link Handler}
130      */
131     private class UiHandler extends Handler {
132
133         public static final int MSG_UPDATE_PROGRESS = 1000;
134
135         @Override
136         public void handleMessage(Message msg) {
137             switch (msg.what) {
138                 case MSG_UPDATE_PROGRESS:
139                     updateProgressForPlayer();
140                     break;
141                 default:
142                     super.handleMessage(msg);
143             }
144         }
145
146     }
147
148     // Members
149     private final BroadcastReceiver mAudioNoisyReceiver = new BroadcastReceiver() {
150         @Override
151         public void onReceive(Context context, Intent intent) {
152             // [NOTE][MSB]: Handle any audio output changes
153             if (intent != null) {
154                 if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
155                     pausePlayback();
156                 }
157             }
158         }
159     };
160     private UiHandler mHandler = new UiHandler();
161     private static AsyncQueryHandler sAsyncQueryHandler;
162     private AudioManager mAudioManager;
163     private PreviewPlayer mPreviewPlayer;
164     private PreviewSong mPreviewSong = new PreviewSong();
165     private int mDuration = 0;
166     private int mLastOrientationWhileBuffering;
167
168     // Views
169     private TextView mTitleTextView;
170     private TextView mArtistTextView;
171     private SeekBar mSeekBar;
172     private ProgressBar mProgressBar;
173     private ImageButton mPlayPauseBtn;
174     private View mContainerView;
175
176     // Flags
177     private boolean mIsReceiverRegistered = false;
178     private State mCurrentState = State.INIT;
179
180     @Override
181     public void onCreate(Bundle savedInstanceState) {
182         overridePendingTransition(0, 0);
183         super.onCreate(savedInstanceState);
184         mLastOrientationWhileBuffering = getRequestedOrientation();
185         Logger.logd(TAG, "onCreate(" + savedInstanceState + ")");
186         Intent intent = getIntent();
187         if (intent == null) {
188             Logger.loge(TAG, "No intent");
189             finish();
190             return;
191         }
192         Uri uri = intent.getData();
193         if (uri == null) {
194             Logger.loge(TAG, "No uri data");
195             finish();
196             return;
197         }
198         Logger.logd(TAG, "URI: " + uri);
199         mPreviewSong.URI = uri;
200         PreviewPlayer localPlayer = (PreviewPlayer) getLastNonConfigurationInstance();
201         if (localPlayer == null) {
202             mPreviewPlayer = new PreviewPlayer();
203             mPreviewPlayer.setCallbackActivity(this);
204             try {
205                 mPreviewPlayer.setDataSourceAndPrepare(mPreviewSong.URI);
206             } catch (IOException e) {
207                 Logger.loge(TAG, e.getMessage());
208                 onError(mPreviewPlayer, MediaPlayer.MEDIA_ERROR_IO, 0);
209                 return;
210             }
211         } else {
212             mPreviewPlayer = localPlayer;
213             mPreviewPlayer.setCallbackActivity(this);
214         }
215         mAudioManager = ((AudioManager) getSystemService(Context.AUDIO_SERVICE));
216         sAsyncQueryHandler = new AsyncQueryHandler(getContentResolver()) {
217             @Override
218             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
219                 AudioPreviewActivity.this.onQueryComplete(token, cookie, cursor);
220             }
221         };
222         initializeInterface();
223         registerNoisyAudioReceiver();
224         if (savedInstanceState == null) {
225             processUri();
226         } else {
227             mPreviewSong.TITLE = savedInstanceState.getString(Media.TITLE);
228             mPreviewSong.ARTIST = savedInstanceState.getString(Media.ARTIST);
229             setNames();
230         }
231         if (localPlayer != null) {
232             sendStateChange(State.PREPARED);
233             if (localPlayer.isPlaying()) {
234                 startProgressUpdates();
235                 sendStateChange(State.PLAYING);
236             }
237         }
238     }
239
240     @Override
241     public void onSaveInstanceState(Bundle outState) {
242         if (mIsReceiverRegistered) {
243             unregisterReceiver(mAudioNoisyReceiver);
244             mIsReceiverRegistered = false;
245         }
246         outState.putString(Media.TITLE, mPreviewSong.TITLE);
247         outState.putString(Media.ARTIST, mPreviewSong.ARTIST);
248         super.onSaveInstanceState(outState);
249     }
250
251     @Override
252     public Object onRetainNonConfigurationInstance() {
253         mPreviewPlayer.clearCallbackActivity();
254         PreviewPlayer localPlayer = mPreviewPlayer;
255         mPreviewPlayer = null;
256         return localPlayer;
257     }
258
259     @Override
260     public void onPause() {
261         overridePendingTransition(0, 0);
262         super.onPause();
263     }
264
265     @Override
266     public void onDestroy() {
267         if (mIsReceiverRegistered) {
268             unregisterReceiver(mAudioNoisyReceiver);
269             mIsReceiverRegistered = false;
270         }
271         stopPlaybackAndTeardown();
272         super.onDestroy();
273     }
274
275     private void sendStateChange(State newState) {
276         mCurrentState = newState;
277         handleStateChangeForUi();
278     }
279
280     private void handleStateChangeForUi() {
281         switch (mCurrentState) {
282             case INIT:
283                 Logger.logd(TAG, "INIT");
284                 break;
285             case PREPARED:
286                 Logger.logd(TAG, "PREPARED");
287                 if (mPreviewPlayer != null) {
288                     mDuration = mPreviewPlayer.getDuration();
289                 }
290                 if (mDuration > 0 && mSeekBar != null) {
291                     mSeekBar.setMax(mDuration);
292                     mSeekBar.setEnabled(true);
293                     mSeekBar.setVisibility(View.VISIBLE);
294                 }
295                 if (mProgressBar != null) {
296                     mProgressBar.setVisibility(View.INVISIBLE);
297                     setRequestedOrientation(mLastOrientationWhileBuffering);
298                 }
299                 if (mPlayPauseBtn != null) {
300                     mPlayPauseBtn.setImageResource(R.drawable.btn_playback_play);
301                     mPlayPauseBtn.setEnabled(true);
302                     mPlayPauseBtn.setOnClickListener(this);
303                 }
304                 break;
305             case PLAYING:
306                 Logger.logd(TAG, "PLAYING");
307                 if (mPlayPauseBtn != null) {
308                     mPlayPauseBtn.setImageResource(R.drawable.btn_playback_pause);
309                     mPlayPauseBtn.setEnabled(true);
310                 }
311                 break;
312             case PAUSED:
313                 Logger.logd(TAG, "PAUSED");
314                 if (mPlayPauseBtn != null) {
315                     mPlayPauseBtn.setImageResource(R.drawable.btn_playback_play);
316                     mPlayPauseBtn.setEnabled(true);
317                 }
318                 break;
319         }
320         setNames();
321     }
322
323     private void onQueryComplete(int token, Object cookie, Cursor cursor) {
324         String title = null;
325         String artist = null;
326         if (cursor == null || cursor.getCount() < 1) {
327             Logger.loge(TAG, "Null or empty cursor!");
328             return;
329         }
330         boolean moved = cursor.moveToFirst();
331         if (!moved) {
332             Logger.loge(TAG, "Failed to read cursor!");
333             return;
334         }
335         int index = -1;
336         switch (token) {
337             case CONTENT_QUERY_TOKEN:
338                 index = cursor.getColumnIndex(Media.TITLE);
339                 if (index > -1) {
340                     title = cursor.getString(index);
341                 }
342                 index = cursor.getColumnIndex(Media.ARTIST);
343                 if (index > -1) {
344                     artist = cursor.getString(index);
345                 }
346                 break;
347             case CONTENT_BAD_QUERY_TOKEN:
348                 index = cursor.getColumnIndex(Media.DISPLAY_NAME);
349                 if (index > -1) {
350                     title = cursor.getString(index);
351                 }
352                 break;
353             default:
354                 title = null;
355                 break;
356         }
357         cursor.close();
358
359         // Well if we didn't get the name lets fallback to something else
360         if (TextUtils.isEmpty(title)) {
361             title = getNameFromPath();
362         }
363
364         mPreviewSong.TITLE = title;
365         mPreviewSong.ARTIST = artist;
366
367         setNames();
368     }
369
370     private String getNameFromPath() {
371         String path = "Unknown"; // [TODO][MSB]: Localize
372         if (mPreviewSong != null) {
373             if (mPreviewSong.URI != null) {
374                 path = mPreviewSong.URI.getLastPathSegment();
375             }
376         }
377         return path;
378     }
379
380     private void setNames() {
381         // Set the text
382         mTitleTextView.setText(mPreviewSong.TITLE);
383         mArtistTextView.setText(mPreviewSong.ARTIST);
384     }
385
386     private void initializeInterface() {
387         setVolumeControlStream(AudioManager.STREAM_MUSIC);
388         requestWindowFeature(Window.FEATURE_NO_TITLE);
389         setContentView(R.layout.activity_audio_preview);
390         mContainerView = findViewById(R.id.grp_container_view);
391         // Make it so if the user touches the background overlay we exit
392         View v = findViewById(R.id.grp_transparent_wrapper);
393         v.setOnTouchListener(this);
394         mTitleTextView = (TextView) findViewById(R.id.tv_title);
395         mArtistTextView = (TextView) findViewById(R.id.tv_artist);
396         mSeekBar = (SeekBar) findViewById(R.id.sb_progress);
397         mSeekBar.setOnSeekBarChangeListener(this);
398         mProgressBar = (ProgressBar) findViewById(R.id.pb_loader);
399         mPlayPauseBtn = (ImageButton) findViewById(R.id.ib_playpause);
400     }
401
402     private void processUri() {
403         String scheme = mPreviewSong.URI.getScheme();
404         Logger.logd(TAG, "Uri Scheme: " + scheme);
405         if (SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
406             handleContentScheme();
407         } else if (SCHEME_FILE.equalsIgnoreCase(scheme)) {
408             handleFileScheme();
409         } else if (SCHEME_HTTP.equalsIgnoreCase(scheme)) {
410             handleHttpScheme();
411         }
412     }
413
414     private void startProgressUpdates() {
415         if (mHandler != null) {
416             mHandler.removeMessages(UiHandler.MSG_UPDATE_PROGRESS);
417             Message msg = mHandler.obtainMessage(UiHandler.MSG_UPDATE_PROGRESS);
418             mHandler.sendMessage(msg);
419         }
420     }
421
422     private void updateProgressForPlayer() {
423         if (mSeekBar != null && mPreviewPlayer != null) {
424             if (mPreviewPlayer.isPrepared()) {
425                 mSeekBar.setProgress(mPreviewPlayer.getCurrentPosition());
426             }
427         }
428         if (mHandler != null) {
429             mHandler.removeMessages(UiHandler.MSG_UPDATE_PROGRESS);
430             Message msg = mHandler.obtainMessage(UiHandler.MSG_UPDATE_PROGRESS);
431             mHandler.sendMessageDelayed(msg, PROGRESS_DELAY_INTERVAL);
432         }
433     }
434
435     private void handleContentScheme() {
436         String authority = mPreviewSong.URI.getAuthority();
437         if (!AUTHORITY_MEDIA.equalsIgnoreCase(authority)) {
438             Logger.logd(TAG, "Bad authority!");
439             sAsyncQueryHandler
440                     .startQuery(CONTENT_BAD_QUERY_TOKEN, null, mPreviewSong.URI, null, null, null,
441                             null);
442         } else {
443             sAsyncQueryHandler
444                     .startQuery(CONTENT_QUERY_TOKEN, null, mPreviewSong.URI, MEDIA_PROJECTION, null,
445                             null, null);
446         }
447     }
448
449     private void handleFileScheme() {
450         String path = mPreviewSong.URI.getPath();
451         sAsyncQueryHandler.startQuery(CONTENT_QUERY_TOKEN, null, Media.EXTERNAL_CONTENT_URI,
452                 MEDIA_PROJECTION, "_data=?", new String[] { path }, null);
453     }
454
455     private void handleHttpScheme() {
456         if (mProgressBar != null) {
457             mProgressBar.setVisibility(View.VISIBLE);
458             mLastOrientationWhileBuffering = getRequestedOrientation();
459             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
460         }
461         String path = getNameFromPath();
462         mPreviewSong.TITLE = path;
463         setNames();
464     }
465
466     private void registerNoisyAudioReceiver() {
467         IntentFilter localIntentFilter = new IntentFilter();
468         localIntentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
469         registerReceiver(this.mAudioNoisyReceiver, localIntentFilter);
470         mIsReceiverRegistered = true;
471     }
472
473     @Override
474     public void onCompletion(MediaPlayer mp) {
475         mHandler.removeMessages(UiHandler.MSG_UPDATE_PROGRESS);
476         if (mSeekBar != null && mPreviewPlayer != null) {
477             mSeekBar.setProgress(mPreviewPlayer.getCurrentPosition());
478         }
479         sendStateChange(State.PREPARED);
480     }
481
482     @Override
483     public boolean onError(MediaPlayer mp, int what, int extra) {
484         switch (what) {
485             case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
486                 Toast.makeText(this, "Server has died!", Toast.LENGTH_SHORT).show();
487                 break;
488             case MediaPlayer.MEDIA_ERROR_IO:
489                 Toast.makeText(this, "I/O error!", Toast.LENGTH_SHORT).show();
490                 break;
491             case MediaPlayer.MEDIA_ERROR_MALFORMED:
492                 Toast.makeText(this, "Malformed media!", Toast.LENGTH_SHORT).show();
493                 break;
494             case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
495                 Toast.makeText(this, "Not valid for progressive playback!", Toast.LENGTH_SHORT)
496                         .show();
497                 break;
498             case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
499                 Toast.makeText(this, "Media server timed out!", Toast.LENGTH_SHORT).show();
500                 break;
501             case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
502                 Toast.makeText(this, "Media is unsupported!", Toast.LENGTH_SHORT).show();
503                 break;
504             case MediaPlayer.MEDIA_ERROR_UNKNOWN:
505             default:
506                 Toast.makeText(this, "An unkown error has occurred: " + what, Toast.LENGTH_LONG)
507                         .show();
508                 break;
509         }
510         stopPlaybackAndTeardown();
511         finish();
512         return true; // false causes flow to not call onCompletion
513     }
514
515     @Override
516     public void onPrepared(MediaPlayer mp) {
517         sendStateChange(State.PREPARED);
518         startPlayback();
519     }
520
521     @Override
522     public boolean onTouch(View v, MotionEvent event) {
523         int x = (int) event.getX();
524         int y = (int) event.getY();
525         int containerX1 = (int) mContainerView.getX();
526         int containerY1 = (int) mContainerView.getY();
527         int containerX2 = (int) (mContainerView.getX() + mContainerView.getWidth());
528         int containerY2 = (int) (mContainerView.getY() + mContainerView.getHeight());
529
530         Rect r = new Rect();
531         r.set(containerX1, containerY1, containerX2, containerY2);
532         if (!r.contains(x, y)) {
533             stopPlaybackAndTeardown();
534             finish();
535         }
536
537         return false;
538     }
539
540     @Override
541     public void onClick(View v) {
542         switch (v.getId()) {
543             case R.id.ib_playpause:
544                 if (mCurrentState == State.PREPARED || mCurrentState == State.PAUSED) {
545                     startPlayback();
546                 } else {
547                     pausePlayback();
548                 }
549                 break;
550             case R.id.grp_transparent_wrapper:
551                 stopPlaybackAndTeardown();
552                 finish();
553                 break;
554             default:
555                 break;
556         }
557     }
558
559     private boolean gainAudioFocus() {
560         if (mAudioManager == null) {
561             return false;
562         }
563         int r = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
564                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
565         return r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
566     }
567
568     private void abandonAudioFocus() {
569         if (mAudioManager != null) {
570             mAudioManager.abandonAudioFocus(this);
571         }
572     }
573
574     private void startPlayback() {
575         if (mPreviewPlayer != null && !mPreviewPlayer.isPlaying()) {
576             if (mPreviewPlayer.isPrepared()) {
577                 if (gainAudioFocus()) {
578                     mPreviewPlayer.start();
579                     sendStateChange(State.PLAYING);
580                     startProgressUpdates();
581                 } else {
582                     Logger.loge(TAG, "Failed to gain audio focus!");
583                     onError(mPreviewPlayer, MediaPlayer.MEDIA_ERROR_TIMED_OUT, 0);
584                 }
585             } else {
586                 Logger.loge(TAG, "Not prepared!");
587             }
588         } else {
589             Logger.logd(TAG, "No player or is not playing!");
590         }
591     }
592
593     private void stopPlaybackAndTeardown() {
594         if (mPreviewPlayer != null) {
595             if (mPreviewPlayer.isPlaying()) {
596                 mPreviewPlayer.stop();
597             }
598             mPreviewPlayer.release();
599             mPreviewPlayer = null;
600         }
601         abandonAudioFocus();
602     }
603
604     private void pausePlayback() {
605         pausePlayback(true);
606     }
607
608     private void pausePlayback(boolean updateUi) {
609         if (mPreviewPlayer != null && mPreviewPlayer.isPlaying()) {
610             mPreviewPlayer.pause();
611             if (updateUi) {
612                 sendStateChange(State.PAUSED);
613             }
614             mHandler.removeMessages(UiHandler.MSG_UPDATE_PROGRESS);
615         }
616     }
617
618     @Override
619     public void onAudioFocusChange(int focusChange) {
620         if (mPreviewPlayer == null) {
621             if (mAudioManager != null) {
622                 mAudioManager.abandonAudioFocus(this);
623             }
624         }
625         Logger.logd(TAG, "Focus change: " + focusChange);
626         switch (focusChange) {
627             case AudioManager.AUDIOFOCUS_LOSS:
628                 stopPlaybackAndTeardown();
629                 finish();
630                 break;
631             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
632                 pausePlayback();
633                 break;
634             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
635                 mPreviewPlayer.setVolume(0.2f, 0.2f);
636                 break;
637             case AudioManager.AUDIOFOCUS_GAIN:
638                 mPreviewPlayer.setVolume(1.0f, 1.0f);
639                 startPlayback();
640                 break;
641             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
642             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
643                 break;
644         }
645     }
646
647     @Override
648     public void onUserLeaveHint() {
649         stopPlaybackAndTeardown();
650         finish();
651         super.onUserLeaveHint();
652     }
653
654     @Override
655     public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
656         boolean result = true;
657         switch (keyCode) {
658             case KeyEvent.KEYCODE_HEADSETHOOK:
659                 pausePlayback();
660                 break;
661             case KeyEvent.KEYCODE_MEDIA_NEXT:
662             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
663             case KeyEvent.KEYCODE_MEDIA_REWIND:
664             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
665                 return result;
666             case KeyEvent.KEYCODE_MEDIA_PLAY:
667                 startPlayback();
668                 return result;
669             case KeyEvent.KEYCODE_MEDIA_PAUSE:
670                 pausePlayback();
671                 return result;
672             case KeyEvent.KEYCODE_VOLUME_UP:
673             case KeyEvent.KEYCODE_VOLUME_DOWN:
674             case KeyEvent.KEYCODE_VOLUME_MUTE:
675                 result = super.onKeyDown(keyCode, keyEvent);
676                 return result;
677             default:
678                 result = super.onKeyDown(keyCode, keyEvent);
679                 break;
680         }
681         stopPlaybackAndTeardown();
682         finish();
683         return result;
684     }
685
686     /**
687      * <pre>
688      *     Media player specifically tweaked for use in this audio preview context
689      * </pre>
690      */
691     private static class PreviewPlayer extends MediaPlayer
692             implements MediaPlayer.OnPreparedListener {
693
694         // Members
695         private WeakReference<AudioPreviewActivity> mActivityReference; // weakref from static class
696         private boolean mIsPrepared = false;
697
698         /* package */ boolean isPrepared() {
699             return mIsPrepared;
700         }
701
702         /* package */ PreviewPlayer() {
703             setOnPreparedListener(this);
704         }
705
706         /* package */ void clearCallbackActivity() {
707             mActivityReference.clear();
708             mActivityReference = null;
709             setOnErrorListener(null);
710             setOnCompletionListener(null);
711         }
712
713         /* package */ void setCallbackActivity(AudioPreviewActivity activity)
714                 throws IllegalArgumentException{
715             if (activity == null) {
716                 throw new IllegalArgumentException("'activity' cannot be null!");
717             }
718             mActivityReference = new WeakReference<AudioPreviewActivity>(activity);
719             setOnErrorListener(activity);
720             setOnCompletionListener(activity);
721         }
722
723         /* package */ void setDataSourceAndPrepare(Uri uri)
724                 throws IllegalArgumentException, IOException {
725             if (uri == null || uri.toString().length() < 1) {
726                 throw new IllegalArgumentException("'uri' cannot be null or empty!");
727             }
728             AudioPreviewActivity activity = mActivityReference.get();
729             if (activity != null && !activity.isFinishing()) {
730                 setDataSource(activity, uri);
731                 prepareAsync();
732             }
733         }
734
735         @Override
736         public void onPrepared(MediaPlayer mp) {
737             mIsPrepared = true;
738             if (mActivityReference != null) {
739                 AudioPreviewActivity activity = mActivityReference.get();
740                 if (activity != null && !activity.isFinishing()) {
741                     activity.onPrepared(mp);
742                 }
743             }
744         }
745
746     }
747 }