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