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