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