• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle icicle)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
onRetainNonConfigurationInstance()199     public Object onRetainNonConfigurationInstance() {
200         PreviewPlayer player = mPlayer;
201         mPlayer = null;
202         return player;
203     }
204 
205     @Override
onDestroy()206     public void onDestroy() {
207         stopPlayback();
208         super.onDestroy();
209     }
210 
stopPlayback()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
onUserLeaveHint()223     public void onUserLeaveHint() {
224         stopPlayback();
225         finish();
226         super.onUserLeaveHint();
227     }
228 
onPrepared(MediaPlayer mp)229     public void onPrepared(MediaPlayer mp) {
230         if (isFinishing()) return;
231         mPlayer = (PreviewPlayer) mp;
232         setNames();
233         mPlayer.start();
234         showPostPrepareUI();
235     }
236 
showPostPrepareUI()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 
start()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 
setNames()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 
run()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 
updatePlayPause()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             mPlayer.seekTo(progress);
337         }
338         public void onStopTrackingTouch(SeekBar bar) {
339             mSeeking = false;
340         }
341     };
342 
onError(MediaPlayer mp, int what, int extra)343     public boolean onError(MediaPlayer mp, int what, int extra) {
344         Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
345         finish();
346         return true;
347     }
348 
onCompletion(MediaPlayer mp)349     public void onCompletion(MediaPlayer mp) {
350         mSeekBar.setProgress(mDuration);
351         updatePlayPause();
352     }
353 
playPauseClicked(View v)354     public void playPauseClicked(View v) {
355         if (mPlayer.isPlaying()) {
356             mPlayer.pause();
357         } else {
358             start();
359         }
360         updatePlayPause();
361     }
362 
363     @Override
onCreateOptionsMenu(Menu menu)364     public boolean onCreateOptionsMenu(Menu menu) {
365         super.onCreateOptionsMenu(menu);
366         // TODO: if mMediaId != -1, then the playing file has an entry in the media
367         // database, and we could open it in the full music app instead.
368         // Ideally, we would hand off the currently running mediaplayer
369         // to the music UI, which can probably be done via a public static
370         menu.add(0, OPEN_IN_MUSIC, 0, "open in music");
371         return true;
372     }
373 
374     @Override
onPrepareOptionsMenu(Menu menu)375     public boolean onPrepareOptionsMenu(Menu menu) {
376         MenuItem item = menu.findItem(OPEN_IN_MUSIC);
377         if (mMediaId >= 0) {
378             item.setVisible(true);
379             return true;
380         }
381         item.setVisible(false);
382         return false;
383     }
384 
385     @Override
onKeyDown(int keyCode, KeyEvent event)386     public boolean onKeyDown(int keyCode, KeyEvent event) {
387         switch (keyCode) {
388             case KeyEvent.KEYCODE_HEADSETHOOK:
389             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
390                 if (mPlayer.isPlaying()) {
391                     mPlayer.pause();
392                 } else {
393                     start();
394                 }
395                 updatePlayPause();
396                 return true;
397             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
398             case KeyEvent.KEYCODE_MEDIA_NEXT:
399             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
400             case KeyEvent.KEYCODE_MEDIA_REWIND:
401                 return true;
402             case KeyEvent.KEYCODE_MEDIA_STOP:
403             case KeyEvent.KEYCODE_BACK:
404                 stopPlayback();
405                 finish();
406                 return true;
407         }
408         return super.onKeyDown(keyCode, event);
409     }
410 
411     /*
412      * Wrapper class to help with handing off the MediaPlayer to the next instance
413      * of the activity in case of orientation change, without losing any state.
414      */
415     private static class PreviewPlayer extends MediaPlayer implements OnPreparedListener {
416         AudioPreview mActivity;
417         boolean mIsPrepared = false;
418 
setActivity(AudioPreview activity)419         public void setActivity(AudioPreview activity) {
420             mActivity = activity;
421             setOnPreparedListener(this);
422             setOnErrorListener(mActivity);
423             setOnCompletionListener(mActivity);
424         }
425 
setDataSourceAndPrepare(Uri uri)426         public void setDataSourceAndPrepare(Uri uri) throws IllegalArgumentException,
427                         SecurityException, IllegalStateException, IOException {
428             setDataSource(mActivity,uri);
429             prepareAsync();
430         }
431 
432         /* (non-Javadoc)
433          * @see android.media.MediaPlayer.OnPreparedListener#onPrepared(android.media.MediaPlayer)
434          */
435         @Override
onPrepared(MediaPlayer mp)436         public void onPrepared(MediaPlayer mp) {
437             mIsPrepared = true;
438             mActivity.onPrepared(mp);
439         }
440 
isPrepared()441         boolean isPrepared() {
442             return mIsPrepared;
443         }
444     }
445 
446 }
447