Home | History | Annotate | Download | only in music
      1 /*
      2  * Copyright (C) 2010 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 
     17 package com.android.music;
     18 
     19 import android.app.Activity;
     20 import android.content.AsyncQueryHandler;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.database.Cursor;
     25 import android.media.AudioManager;
     26 import android.media.MediaPlayer;
     27 import android.media.AudioManager.OnAudioFocusChangeListener;
     28 import android.media.MediaPlayer.OnCompletionListener;
     29 import android.media.MediaPlayer.OnErrorListener;
     30 import android.media.MediaPlayer.OnPreparedListener;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.provider.MediaStore;
     35 import android.provider.OpenableColumns;
     36 import android.text.TextUtils;
     37 import android.util.Log;
     38 import android.view.KeyEvent;
     39 import android.view.Menu;
     40 import android.view.MenuItem;
     41 import android.view.View;
     42 import android.view.Window;
     43 import android.view.WindowManager;
     44 import android.widget.ImageButton;
     45 import android.widget.ProgressBar;
     46 import android.widget.SeekBar;
     47 import android.widget.TextView;
     48 import android.widget.SeekBar.OnSeekBarChangeListener;
     49 import android.widget.Toast;
     50 
     51 import java.io.IOException;
     52 
     53 /**
     54  * Dialog that comes up in response to various music-related VIEW intents.
     55  */
     56 public class AudioPreview extends Activity implements OnPreparedListener, OnErrorListener, OnCompletionListener
     57 {
     58     private final static String TAG = "AudioPreview";
     59     private PreviewPlayer mPlayer;
     60     private TextView mTextLine1;
     61     private TextView mTextLine2;
     62     private TextView mLoadingText;
     63     private SeekBar mSeekBar;
     64     private Handler mProgressRefresher;
     65     private boolean mSeeking = false;
     66     private boolean mUiPaused = true;
     67     private int mDuration;
     68     private Uri mUri;
     69     private long mMediaId = -1;
     70     private static final int OPEN_IN_MUSIC = 1;
     71     private AudioManager mAudioManager;
     72     private boolean mPausedByTransientLossOfFocus;
     73 
     74     @Override
     75     public void onCreate(Bundle icicle) {
     76         super.onCreate(icicle);
     77 
     78         Intent intent = getIntent();
     79         if (intent == null) {
     80             finish();
     81             return;
     82         }
     83         mUri = intent.getData();
     84         if (mUri == null) {
     85             finish();
     86             return;
     87         }
     88         String scheme = mUri.getScheme();
     89 
     90         setVolumeControlStream(AudioManager.STREAM_MUSIC);
     91         requestWindowFeature(Window.FEATURE_NO_TITLE);
     92         setContentView(R.layout.audiopreview);
     93 
     94         mTextLine1 = (TextView) findViewById(R.id.line1);
     95         mTextLine2 = (TextView) findViewById(R.id.line2);
     96         mLoadingText = (TextView) findViewById(R.id.loading);
     97         if (scheme.equals("http")) {
     98             String msg = getString(R.string.streamloadingtext, mUri.getHost());
     99             mLoadingText.setText(msg);
    100         } else {
    101             mLoadingText.setVisibility(View.GONE);
    102         }
    103         mSeekBar = (SeekBar) findViewById(R.id.progress);
    104         mProgressRefresher = new Handler();
    105         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    106 
    107         PreviewPlayer player = (PreviewPlayer) getLastNonConfigurationInstance();
    108         if (player == null) {
    109             mPlayer = new PreviewPlayer();
    110             mPlayer.setActivity(this);
    111             try {
    112                 mPlayer.setDataSourceAndPrepare(mUri);
    113             } catch (Exception ex) {
    114                 // catch generic Exception, since we may be called with a media
    115                 // content URI, another content provider's URI, a file URI,
    116                 // an http URI, and there are different exceptions associated
    117                 // with failure to open each of those.
    118                 Log.d(TAG, "Failed to open file: " + ex);
    119                 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
    120                 finish();
    121                 return;
    122             }
    123         } else {
    124             mPlayer = player;
    125             mPlayer.setActivity(this);
    126             // onResume will update the UI
    127         }
    128 
    129         AsyncQueryHandler mAsyncQueryHandler = new AsyncQueryHandler(getContentResolver()) {
    130             @Override
    131             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    132                 if (cursor != null && cursor.moveToFirst()) {
    133 
    134                     int titleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
    135                     int artistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
    136                     int idIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
    137                     int displaynameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    138 
    139                     if (idIdx >=0) {
    140                         mMediaId = cursor.getLong(idIdx);
    141                     }
    142 
    143                     if (titleIdx >= 0) {
    144                         String title = cursor.getString(titleIdx);
    145                         mTextLine1.setText(title);
    146                         if (artistIdx >= 0) {
    147                             String artist = cursor.getString(artistIdx);
    148                             mTextLine2.setText(artist);
    149                         }
    150                     } else if (displaynameIdx >= 0) {
    151                         String name = cursor.getString(displaynameIdx);
    152                         mTextLine1.setText(name);
    153                     } else {
    154                         // Couldn't find anything to display, what to do now?
    155                         Log.w(TAG, "Cursor had no names for us");
    156                     }
    157                 } else {
    158                     Log.w(TAG, "empty cursor");
    159                 }
    160 
    161                 if (cursor != null) {
    162                     cursor.close();
    163                 }
    164                 setNames();
    165             }
    166         };
    167 
    168         if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
    169             if (mUri.getAuthority() == MediaStore.AUTHORITY) {
    170                 // try to get title and artist from the media content provider
    171                 mAsyncQueryHandler.startQuery(0, null, mUri, new String [] {
    172                         MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST},
    173                         null, null, null);
    174             } else {
    175                 // Try to get the display name from another content provider.
    176                 // Don't specifically ask for the display name though, since the
    177                 // provider might not actually support that column.
    178                 mAsyncQueryHandler.startQuery(0, null, mUri, null, null, null, null);
    179             }
    180         } else if (scheme.equals("file")) {
    181             // check if this file is in the media database (clicking on a download
    182             // in the download manager might follow this path
    183             String path = mUri.getPath();
    184             mAsyncQueryHandler.startQuery(0, null,  MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    185                     new String [] {MediaStore.Audio.Media._ID,
    186                         MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST},
    187                     MediaStore.Audio.Media.DATA + "=?", new String [] {path}, null);
    188         } else {
    189             // We can't get metadata from the file/stream itself yet, because
    190             // that API is hidden, so instead we display the URI being played
    191             if (mPlayer.isPrepared()) {
    192                 setNames();
    193             }
    194         }
    195     }
    196 
    197     @Override
    198     public void onPause() {
    199         super.onPause();
    200         mUiPaused = true;
    201         if (mProgressRefresher != null) {
    202             mProgressRefresher.removeCallbacksAndMessages(null);
    203         }
    204     }
    205 
    206     @Override
    207     public void onResume() {
    208         super.onResume();
    209         mUiPaused = false;
    210         if (mPlayer.isPrepared()) {
    211             showPostPrepareUI();
    212         }
    213     }
    214 
    215     @Override
    216     public Object onRetainNonConfigurationInstance() {
    217         PreviewPlayer player = mPlayer;
    218         mPlayer = null;
    219         return player;
    220     }
    221 
    222     @Override
    223     public void onDestroy() {
    224         stopPlayback();
    225         super.onDestroy();
    226     }
    227 
    228     private void stopPlayback() {
    229         if (mProgressRefresher != null) {
    230             mProgressRefresher.removeCallbacksAndMessages(null);
    231         }
    232         if (mPlayer != null) {
    233             mPlayer.release();
    234             mPlayer = null;
    235             mAudioManager.abandonAudioFocus(mAudioFocusListener);
    236         }
    237     }
    238 
    239     @Override
    240     public void onUserLeaveHint() {
    241         stopPlayback();
    242         finish();
    243         super.onUserLeaveHint();
    244     }
    245 
    246     public void onPrepared(MediaPlayer mp) {
    247         if (isFinishing()) return;
    248         mPlayer = (PreviewPlayer) mp;
    249         setNames();
    250         mPlayer.start();
    251         showPostPrepareUI();
    252     }
    253 
    254     private void showPostPrepareUI() {
    255         ProgressBar pb = (ProgressBar) findViewById(R.id.spinner);
    256         pb.setVisibility(View.GONE);
    257         mDuration = mPlayer.getDuration();
    258         if (mDuration != 0) {
    259             mSeekBar.setMax(mDuration);
    260             mSeekBar.setVisibility(View.VISIBLE);
    261             if (!mSeeking) {
    262                 mSeekBar.setProgress(mPlayer.getCurrentPosition());
    263             }
    264         }
    265         mSeekBar.setOnSeekBarChangeListener(mSeekListener);
    266         mLoadingText.setVisibility(View.GONE);
    267         View v = findViewById(R.id.titleandbuttons);
    268         v.setVisibility(View.VISIBLE);
    269         mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
    270                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    271         if (mProgressRefresher != null) {
    272             mProgressRefresher.removeCallbacksAndMessages(null);
    273             mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
    274         }
    275         updatePlayPause();
    276     }
    277 
    278     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
    279         public void onAudioFocusChange(int focusChange) {
    280             if (mPlayer == null) {
    281                 // this activity has handed its MediaPlayer off to the next activity
    282                 // (e.g. portrait/landscape switch) and should abandon its focus
    283                 mAudioManager.abandonAudioFocus(this);
    284                 return;
    285             }
    286             switch (focusChange) {
    287                 case AudioManager.AUDIOFOCUS_LOSS:
    288                     mPausedByTransientLossOfFocus = false;
    289                     mPlayer.pause();
    290                     break;
    291                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    292                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    293                     if (mPlayer.isPlaying()) {
    294                         mPausedByTransientLossOfFocus = true;
    295                         mPlayer.pause();
    296                     }
    297                     break;
    298                 case AudioManager.AUDIOFOCUS_GAIN:
    299                     if (mPausedByTransientLossOfFocus) {
    300                         mPausedByTransientLossOfFocus = false;
    301                         start();
    302                     }
    303                     break;
    304             }
    305             updatePlayPause();
    306         }
    307     };
    308 
    309     private void start() {
    310         mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
    311                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    312         mPlayer.start();
    313         mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
    314     }
    315 
    316     public void setNames() {
    317         if (TextUtils.isEmpty(mTextLine1.getText())) {
    318             mTextLine1.setText(mUri.getLastPathSegment());
    319         }
    320         if (TextUtils.isEmpty(mTextLine2.getText())) {
    321             mTextLine2.setVisibility(View.GONE);
    322         } else {
    323             mTextLine2.setVisibility(View.VISIBLE);
    324         }
    325     }
    326 
    327     class ProgressRefresher implements Runnable {
    328 
    329         @Override
    330         public void run() {
    331             if (mPlayer != null && !mSeeking && mDuration != 0) {
    332                 mSeekBar.setProgress(mPlayer.getCurrentPosition());
    333             }
    334             mProgressRefresher.removeCallbacksAndMessages(null);
    335             if (!mUiPaused) {
    336                 mProgressRefresher.postDelayed(new ProgressRefresher(), 200);
    337             }
    338         }
    339     }
    340 
    341     private void updatePlayPause() {
    342         ImageButton b = (ImageButton) findViewById(R.id.playpause);
    343         if (b != null && mPlayer != null) {
    344             if (mPlayer.isPlaying()) {
    345                 b.setImageResource(R.drawable.btn_playback_ic_pause_small);
    346             } else {
    347                 b.setImageResource(R.drawable.btn_playback_ic_play_small);
    348                 mProgressRefresher.removeCallbacksAndMessages(null);
    349             }
    350         }
    351     }
    352 
    353     private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
    354         public void onStartTrackingTouch(SeekBar bar) {
    355             mSeeking = true;
    356         }
    357         public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
    358             if (!fromuser) {
    359                 return;
    360             }
    361             // Protection for case of simultaneously tapping on seek bar and exit
    362             if (mPlayer == null) {
    363                 return;
    364             }
    365             mPlayer.seekTo(progress);
    366         }
    367         public void onStopTrackingTouch(SeekBar bar) {
    368             mSeeking = false;
    369         }
    370     };
    371 
    372     public boolean onError(MediaPlayer mp, int what, int extra) {
    373         Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
    374         finish();
    375         return true;
    376     }
    377 
    378     public void onCompletion(MediaPlayer mp) {
    379         mSeekBar.setProgress(mDuration);
    380         updatePlayPause();
    381     }
    382 
    383     public void playPauseClicked(View v) {
    384         // Protection for case of simultaneously tapping on play/pause and exit
    385         if (mPlayer == null) {
    386             return;
    387         }
    388         if (mPlayer.isPlaying()) {
    389             mPlayer.pause();
    390         } else {
    391             start();
    392         }
    393         updatePlayPause();
    394     }
    395 
    396     @Override
    397     public boolean onCreateOptionsMenu(Menu menu) {
    398         super.onCreateOptionsMenu(menu);
    399         // TODO: if mMediaId != -1, then the playing file has an entry in the media
    400         // database, and we could open it in the full music app instead.
    401         // Ideally, we would hand off the currently running mediaplayer
    402         // to the music UI, which can probably be done via a public static
    403         menu.add(0, OPEN_IN_MUSIC, 0, "open in music");
    404         return true;
    405     }
    406 
    407     @Override
    408     public boolean onPrepareOptionsMenu(Menu menu) {
    409         MenuItem item = menu.findItem(OPEN_IN_MUSIC);
    410         if (mMediaId >= 0) {
    411             item.setVisible(true);
    412             return true;
    413         }
    414         item.setVisible(false);
    415         return false;
    416     }
    417 
    418     @Override
    419     public boolean onKeyDown(int keyCode, KeyEvent event) {
    420         switch (keyCode) {
    421             case KeyEvent.KEYCODE_HEADSETHOOK:
    422             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    423                 if (mPlayer.isPlaying()) {
    424                     mPlayer.pause();
    425                 } else {
    426                     start();
    427                 }
    428                 updatePlayPause();
    429                 return true;
    430             case KeyEvent.KEYCODE_MEDIA_PLAY:
    431                 start();
    432                 updatePlayPause();
    433                 return true;
    434             case KeyEvent.KEYCODE_MEDIA_PAUSE:
    435                 if (mPlayer.isPlaying()) {
    436                     mPlayer.pause();
    437                 }
    438                 updatePlayPause();
    439                 return true;
    440             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    441             case KeyEvent.KEYCODE_MEDIA_NEXT:
    442             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    443             case KeyEvent.KEYCODE_MEDIA_REWIND:
    444                 return true;
    445             case KeyEvent.KEYCODE_MEDIA_STOP:
    446             case KeyEvent.KEYCODE_BACK:
    447                 stopPlayback();
    448                 finish();
    449                 return true;
    450         }
    451         return super.onKeyDown(keyCode, event);
    452     }
    453 
    454     /*
    455      * Wrapper class to help with handing off the MediaPlayer to the next instance
    456      * of the activity in case of orientation change, without losing any state.
    457      */
    458     private static class PreviewPlayer extends MediaPlayer implements OnPreparedListener {
    459         AudioPreview mActivity;
    460         boolean mIsPrepared = false;
    461 
    462         public void setActivity(AudioPreview activity) {
    463             mActivity = activity;
    464             setOnPreparedListener(this);
    465             setOnErrorListener(mActivity);
    466             setOnCompletionListener(mActivity);
    467         }
    468 
    469         public void setDataSourceAndPrepare(Uri uri) throws IllegalArgumentException,
    470                         SecurityException, IllegalStateException, IOException {
    471             setDataSource(mActivity,uri);
    472             prepareAsync();
    473         }
    474 
    475         /* (non-Javadoc)
    476          * @see android.media.MediaPlayer.OnPreparedListener#onPrepared(android.media.MediaPlayer)
    477          */
    478         @Override
    479         public void onPrepared(MediaPlayer mp) {
    480             mIsPrepared = true;
    481             mActivity.onPrepared(mp);
    482         }
    483 
    484         boolean isPrepared() {
    485             return mIsPrepared;
    486         }
    487     }
    488 
    489 }
    490