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