1 /* 2 * Copyright (C) 2006 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 android.media; 18 19 import static android.media.Utils.parseVibrationEffect; 20 21 import android.annotation.Nullable; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.ContentProvider; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.res.AssetFileDescriptor; 27 import android.content.res.Resources.NotFoundException; 28 import android.database.Cursor; 29 import android.media.audio.Flags; 30 import android.media.audiofx.HapticGenerator; 31 import android.net.Uri; 32 import android.os.Binder; 33 import android.os.Build; 34 import android.os.RemoteException; 35 import android.os.Trace; 36 import android.os.VibrationAttributes; 37 import android.os.VibrationEffect; 38 import android.os.Vibrator; 39 import android.provider.MediaStore; 40 import android.provider.MediaStore.MediaColumns; 41 import android.provider.Settings; 42 import android.util.Log; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.io.IOException; 47 import java.util.ArrayList; 48 49 /** 50 * Ringtone provides a quick method for playing a ringtone, notification, or 51 * other similar types of sounds. 52 * <p> 53 * For ways of retrieving {@link Ringtone} objects or to show a ringtone 54 * picker, see {@link RingtoneManager}. 55 * 56 * @see RingtoneManager 57 */ 58 public class Ringtone { 59 private static final String TAG = "Ringtone"; 60 private static final boolean LOGD = true; 61 62 private static final String[] MEDIA_COLUMNS = new String[] { 63 MediaStore.Audio.Media._ID, 64 MediaStore.Audio.Media.TITLE 65 }; 66 /** Selection that limits query results to just audio files */ 67 private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR " 68 + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')"; 69 70 // keep references on active Ringtones until stopped or completion listener called. 71 private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>(); 72 73 private static final VibrationAttributes VIBRATION_ATTRIBUTES = 74 new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_RINGTONE).build(); 75 76 private static final int VIBRATION_LOOP_DELAY_MS = 200; 77 78 private final Context mContext; 79 private final AudioManager mAudioManager; 80 private VolumeShaper.Configuration mVolumeShaperConfig; 81 private VolumeShaper mVolumeShaper; 82 83 /** 84 * Flag indicating if we're allowed to fall back to remote playback using 85 * {@link #mRemotePlayer}. Typically this is false when we're the remote 86 * player and there is nobody else to delegate to. 87 */ 88 private final boolean mAllowRemote; 89 private final IRingtonePlayer mRemotePlayer; 90 private final Binder mRemoteToken; 91 92 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 93 private MediaPlayer mLocalPlayer; 94 private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); 95 private HapticGenerator mHapticGenerator; 96 97 @UnsupportedAppUsage 98 private Uri mUri; 99 private String mTitle; 100 101 private AudioAttributes mAudioAttributes = new AudioAttributes.Builder() 102 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 103 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 104 .build(); 105 private boolean mPreferBuiltinDevice; 106 // playback properties, use synchronized with mPlaybackSettingsLock 107 private boolean mIsLooping = false; 108 private float mVolume = 1.0f; 109 private boolean mHapticGeneratorEnabled = false; 110 private final Object mPlaybackSettingsLock = new Object(); 111 private final Vibrator mVibrator; 112 private final boolean mRingtoneVibrationSupported; 113 private VibrationEffect mVibrationEffect; 114 private boolean mIsVibrating; 115 116 /** {@hide} */ 117 @UnsupportedAppUsage Ringtone(Context context, boolean allowRemote)118 public Ringtone(Context context, boolean allowRemote) { 119 mContext = context; 120 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 121 mAllowRemote = allowRemote; 122 mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; 123 mRemoteToken = allowRemote ? new Binder() : null; 124 mVibrator = mContext.getSystemService(Vibrator.class); 125 mRingtoneVibrationSupported = Utils.isRingtoneVibrationSettingsSupported(mContext); 126 } 127 128 /** 129 * Sets the stream type where this ringtone will be played. 130 * 131 * @param streamType The stream, see {@link AudioManager}. 132 * @deprecated use {@link #setAudioAttributes(AudioAttributes)} 133 */ 134 @Deprecated setStreamType(int streamType)135 public void setStreamType(int streamType) { 136 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()"); 137 setAudioAttributes(new AudioAttributes.Builder() 138 .setInternalLegacyStreamType(streamType) 139 .build()); 140 } 141 142 /** 143 * Gets the stream type where this ringtone will be played. 144 * 145 * @return The stream type, see {@link AudioManager}. 146 * @deprecated use of stream types is deprecated, see 147 * {@link #setAudioAttributes(AudioAttributes)} 148 */ 149 @Deprecated getStreamType()150 public int getStreamType() { 151 return AudioAttributes.toLegacyStreamType(mAudioAttributes); 152 } 153 154 /** 155 * Sets the {@link AudioAttributes} for this ringtone. 156 * @param attributes the non-null attributes characterizing this ringtone. 157 */ setAudioAttributes(AudioAttributes attributes)158 public void setAudioAttributes(AudioAttributes attributes) 159 throws IllegalArgumentException { 160 setAudioAttributesField(attributes); 161 // The audio attributes have to be set before the media player is prepared. 162 // Re-initialize it. 163 setUri(mUri, mVolumeShaperConfig); 164 createLocalMediaPlayer(); 165 } 166 167 /** 168 * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create 169 * the media player. 170 * @hide 171 */ setAudioAttributesField(@ullable AudioAttributes attributes)172 public void setAudioAttributesField(@Nullable AudioAttributes attributes) { 173 if (attributes == null) { 174 throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone"); 175 } 176 mAudioAttributes = attributes; 177 } 178 179 /** 180 * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is 181 * the one on which outgoing audio for SIM calls is played. 182 * 183 * @param audioManager the audio manage. 184 * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if 185 * none can be found. 186 */ getBuiltinDevice(AudioManager audioManager)187 private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { 188 AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 189 for (AudioDeviceInfo device : deviceList) { 190 if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { 191 return device; 192 } 193 } 194 return null; 195 } 196 197 /** 198 * Sets the preferred device of the ringtong playback to the built-in device. 199 * 200 * @hide 201 */ preferBuiltinDevice(boolean enable)202 public boolean preferBuiltinDevice(boolean enable) { 203 mPreferBuiltinDevice = enable; 204 if (mLocalPlayer == null) { 205 return true; 206 } 207 return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager)); 208 } 209 210 /** 211 * Creates a local media player for the ringtone using currently set attributes. 212 * @return true if media player creation succeeded or is deferred, 213 * false if it did not succeed and can't be tried remotely. 214 * @hide 215 */ createLocalMediaPlayer()216 public boolean createLocalMediaPlayer() { 217 Trace.beginSection("createLocalMediaPlayer"); 218 if (mUri == null) { 219 Log.e(TAG, "Could not create media player as no URI was provided."); 220 return mAllowRemote && mRemotePlayer != null; 221 } 222 destroyLocalPlayer(); 223 // try opening uri locally before delegating to remote player 224 mLocalPlayer = new MediaPlayer(); 225 try { 226 mLocalPlayer.setDataSource(mContext, mUri); 227 mLocalPlayer.setAudioAttributes(mAudioAttributes); 228 mLocalPlayer.setPreferredDevice( 229 mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null); 230 synchronized (mPlaybackSettingsLock) { 231 applyPlaybackProperties_sync(); 232 } 233 if (mVolumeShaperConfig != null) { 234 mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); 235 } 236 mLocalPlayer.prepare(); 237 238 } catch (SecurityException | IOException e) { 239 destroyLocalPlayer(); 240 if (!mAllowRemote) { 241 Log.w(TAG, "Remote playback not allowed: " + e); 242 } 243 } 244 245 if (LOGD) { 246 if (mLocalPlayer != null) { 247 Log.d(TAG, "Successfully created local player"); 248 } else { 249 Log.d(TAG, "Problem opening; delegating to remote player"); 250 } 251 } 252 Trace.endSection(); 253 return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null); 254 } 255 256 /** 257 * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. 258 * If the ringtone has not been created, it will load based on URI provided at {@link #setUri} 259 * and if not URI has been set, it will assume no haptic channels are present. 260 * @hide 261 */ hasHapticChannels()262 public boolean hasHapticChannels() { 263 // FIXME: support remote player, or internalize haptic channels support and remove entirely. 264 try { 265 android.os.Trace.beginSection("Ringtone.hasHapticChannels"); 266 if (mLocalPlayer != null) { 267 for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) { 268 if (trackInfo.hasHapticChannels()) { 269 return true; 270 } 271 } 272 } 273 } finally { 274 android.os.Trace.endSection(); 275 } 276 return false; 277 } 278 279 /** 280 * Returns whether a local player has been created for this ringtone. 281 * @hide 282 */ 283 @VisibleForTesting hasLocalPlayer()284 public boolean hasLocalPlayer() { 285 return mLocalPlayer != null; 286 } 287 288 /** 289 * Returns the {@link AudioAttributes} used by this object. 290 * @return the {@link AudioAttributes} that were set with 291 * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. 292 */ getAudioAttributes()293 public AudioAttributes getAudioAttributes() { 294 return mAudioAttributes; 295 } 296 297 /** 298 * Sets the player to be looping or non-looping. 299 * @param looping whether to loop or not. 300 */ setLooping(boolean looping)301 public void setLooping(boolean looping) { 302 synchronized (mPlaybackSettingsLock) { 303 mIsLooping = looping; 304 applyPlaybackProperties_sync(); 305 } 306 } 307 308 /** 309 * Returns whether the looping mode was enabled on this player. 310 * @return true if this player loops when playing. 311 */ isLooping()312 public boolean isLooping() { 313 synchronized (mPlaybackSettingsLock) { 314 return mIsLooping; 315 } 316 } 317 318 /** 319 * Sets the volume on this player. 320 * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0 321 * corresponds to no attenuation being applied. 322 */ setVolume(float volume)323 public void setVolume(float volume) { 324 synchronized (mPlaybackSettingsLock) { 325 if (volume < 0.0f) { volume = 0.0f; } 326 if (volume > 1.0f) { volume = 1.0f; } 327 mVolume = volume; 328 applyPlaybackProperties_sync(); 329 } 330 } 331 332 /** 333 * Returns the volume scalar set on this player. 334 * @return a value between 0.0f and 1.0f. 335 */ getVolume()336 public float getVolume() { 337 synchronized (mPlaybackSettingsLock) { 338 return mVolume; 339 } 340 } 341 342 /** 343 * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can 344 * only be enabled on devices that support the effect. 345 * 346 * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false. 347 * @see android.media.audiofx.HapticGenerator#isAvailable() 348 */ setHapticGeneratorEnabled(boolean enabled)349 public boolean setHapticGeneratorEnabled(boolean enabled) { 350 if (!HapticGenerator.isAvailable()) { 351 return false; 352 } 353 synchronized (mPlaybackSettingsLock) { 354 mHapticGeneratorEnabled = enabled; 355 applyPlaybackProperties_sync(); 356 } 357 return true; 358 } 359 360 /** 361 * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not. 362 * @return true if the HapticGenerator is enabled. 363 */ isHapticGeneratorEnabled()364 public boolean isHapticGeneratorEnabled() { 365 synchronized (mPlaybackSettingsLock) { 366 return mHapticGeneratorEnabled; 367 } 368 } 369 370 /** 371 * Must be called synchronized on mPlaybackSettingsLock 372 */ applyPlaybackProperties_sync()373 private void applyPlaybackProperties_sync() { 374 if (mLocalPlayer != null) { 375 mLocalPlayer.setVolume(mVolume); 376 mLocalPlayer.setLooping(mIsLooping); 377 if (mHapticGenerator == null && mHapticGeneratorEnabled) { 378 mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId()); 379 } 380 if (mHapticGenerator != null) { 381 mHapticGenerator.setEnabled(mHapticGeneratorEnabled); 382 } 383 } else if (mAllowRemote && (mRemotePlayer != null)) { 384 try { 385 mRemotePlayer.setPlaybackProperties( 386 mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled); 387 } catch (RemoteException e) { 388 Log.w(TAG, "Problem setting playback properties: ", e); 389 } 390 } else { 391 Log.w(TAG, 392 "Neither local nor remote player available when applying playback properties"); 393 } 394 } 395 396 /** 397 * Returns a human-presentable title for ringtone. Looks in media 398 * content provider. If not in either, uses the filename 399 * 400 * @param context A context used for querying. 401 */ getTitle(Context context)402 public String getTitle(Context context) { 403 if (mTitle != null) return mTitle; 404 return mTitle = getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote); 405 } 406 407 /** 408 * @hide 409 */ getTitle( Context context, Uri uri, boolean followSettingsUri, boolean allowRemote)410 public static String getTitle( 411 Context context, Uri uri, boolean followSettingsUri, boolean allowRemote) { 412 ContentResolver res = context.getContentResolver(); 413 414 String title = null; 415 416 if (uri != null) { 417 String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()); 418 419 if (Settings.AUTHORITY.equals(authority)) { 420 if (followSettingsUri) { 421 Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, 422 RingtoneManager.getDefaultType(uri)); 423 String actualTitle = getTitle( 424 context, actualUri, false /*followSettingsUri*/, allowRemote); 425 title = context 426 .getString(com.android.internal.R.string.ringtone_default_with_actual, 427 actualTitle); 428 } 429 } else { 430 Cursor cursor = null; 431 try { 432 if (MediaStore.AUTHORITY.equals(authority)) { 433 final String mediaSelection = allowRemote ? null : MEDIA_SELECTION; 434 cursor = res.query(uri, MEDIA_COLUMNS, mediaSelection, null, null); 435 if (cursor != null && cursor.getCount() == 1) { 436 cursor.moveToFirst(); 437 return cursor.getString(1); 438 } 439 // missing cursor is handled below 440 } 441 } catch (SecurityException e) { 442 IRingtonePlayer mRemotePlayer = null; 443 if (allowRemote) { 444 AudioManager audioManager = 445 (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 446 mRemotePlayer = audioManager.getRingtonePlayer(); 447 } 448 if (mRemotePlayer != null) { 449 try { 450 title = mRemotePlayer.getTitle(uri); 451 } catch (RemoteException re) { 452 } 453 } 454 } finally { 455 if (cursor != null) { 456 cursor.close(); 457 } 458 cursor = null; 459 } 460 if (title == null) { 461 title = uri.getLastPathSegment(); 462 } 463 } 464 } else { 465 title = context.getString(com.android.internal.R.string.ringtone_silent); 466 } 467 468 if (title == null) { 469 title = context.getString(com.android.internal.R.string.ringtone_unknown); 470 if (title == null) { 471 title = ""; 472 } 473 } 474 475 return title; 476 } 477 478 /** 479 * Set {@link Uri} to be used for ringtone playback. 480 * {@link IRingtonePlayer}. 481 * 482 * @hide 483 */ 484 @UnsupportedAppUsage setUri(Uri uri)485 public void setUri(Uri uri) { 486 setUri(uri, null); 487 } 488 489 /** 490 * @hide 491 */ setVolumeShaperConfig(@ullable VolumeShaper.Configuration volumeShaperConfig)492 public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) { 493 mVolumeShaperConfig = volumeShaperConfig; 494 } 495 496 /** 497 * Set {@link Uri} to be used for ringtone playback. Attempts to open 498 * locally, otherwise will delegate playback to remote 499 * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required. 500 * 501 * @hide 502 */ setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig)503 public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) { 504 mVolumeShaperConfig = volumeShaperConfig; 505 mUri = uri; 506 if (mUri == null) { 507 destroyLocalPlayer(); 508 } 509 if (Flags.enableRingtoneHapticsCustomization() 510 && mRingtoneVibrationSupported && mUri != null) { 511 mVibrationEffect = parseVibrationEffect(mVibrator, Utils.getVibrationUri(mUri)); 512 if (mVibrationEffect != null) { 513 mVibrationEffect = 514 mVibrationEffect.applyRepeatingIndefinitely(true, VIBRATION_LOOP_DELAY_MS); 515 } 516 } 517 } 518 519 /** 520 * Returns the {@link VibrationEffect} has been created for this ringtone. 521 * @hide 522 */ 523 @VisibleForTesting getVibrationEffect()524 public VibrationEffect getVibrationEffect() { 525 return mVibrationEffect; 526 } 527 528 /** {@hide} */ 529 @UnsupportedAppUsage getUri()530 public Uri getUri() { 531 return mUri; 532 } 533 534 /** 535 * Plays the ringtone. 536 */ play()537 public void play() { 538 if (mLocalPlayer != null) { 539 // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone 540 // (typically because ringer mode is vibrate). 541 if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) 542 != 0) { 543 startLocalPlayer(); 544 } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) { 545 // is haptic only ringtone 546 startLocalPlayer(); 547 } 548 } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) { 549 final Uri canonicalUri = mUri.getCanonicalUri(); 550 final boolean looping; 551 final float volume; 552 synchronized (mPlaybackSettingsLock) { 553 looping = mIsLooping; 554 volume = mVolume; 555 } 556 try { 557 mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes, 558 volume, looping, mVolumeShaperConfig); 559 } catch (RemoteException e) { 560 if (!playFallbackRingtone()) { 561 Log.w(TAG, "Problem playing ringtone: " + e); 562 } 563 } 564 } else { 565 if (!playFallbackRingtone()) { 566 Log.w(TAG, "Neither local nor remote playback available"); 567 } 568 } 569 if (Flags.enableRingtoneHapticsCustomization() && mRingtoneVibrationSupported) { 570 playVibration(); 571 } 572 } 573 playVibration()574 private void playVibration() { 575 if (mVibrationEffect == null) { 576 return; 577 } 578 mIsVibrating = true; 579 mVibrator.vibrate(mVibrationEffect, VIBRATION_ATTRIBUTES); 580 } 581 582 /** 583 * Stops a playing ringtone. 584 */ stop()585 public void stop() { 586 if (mLocalPlayer != null) { 587 destroyLocalPlayer(); 588 } else if (mAllowRemote && (mRemotePlayer != null)) { 589 try { 590 mRemotePlayer.stop(mRemoteToken); 591 } catch (RemoteException e) { 592 Log.w(TAG, "Problem stopping ringtone: " + e); 593 } 594 } 595 if (Flags.enableRingtoneHapticsCustomization() 596 && mRingtoneVibrationSupported && mIsVibrating) { 597 mVibrator.cancel(); 598 mIsVibrating = false; 599 } 600 } 601 destroyLocalPlayer()602 private void destroyLocalPlayer() { 603 if (mLocalPlayer != null) { 604 if (mHapticGenerator != null) { 605 mHapticGenerator.release(); 606 mHapticGenerator = null; 607 } 608 mLocalPlayer.setOnCompletionListener(null); 609 mLocalPlayer.reset(); 610 mLocalPlayer.release(); 611 mLocalPlayer = null; 612 mVolumeShaper = null; 613 synchronized (sActiveRingtones) { 614 sActiveRingtones.remove(this); 615 } 616 } 617 } 618 startLocalPlayer()619 private void startLocalPlayer() { 620 if (mLocalPlayer == null) { 621 return; 622 } 623 synchronized (sActiveRingtones) { 624 sActiveRingtones.add(this); 625 } 626 mLocalPlayer.setOnCompletionListener(mCompletionListener); 627 mLocalPlayer.start(); 628 if (mVolumeShaper != null) { 629 mVolumeShaper.apply(VolumeShaper.Operation.PLAY); 630 } 631 } 632 633 /** 634 * Whether this ringtone is currently playing. 635 * 636 * @return True if playing, false otherwise. 637 */ isPlaying()638 public boolean isPlaying() { 639 if (mLocalPlayer != null) { 640 return mLocalPlayer.isPlaying(); 641 } else if (mAllowRemote && (mRemotePlayer != null)) { 642 try { 643 return mRemotePlayer.isPlaying(mRemoteToken); 644 } catch (RemoteException e) { 645 Log.w(TAG, "Problem checking ringtone: " + e); 646 return false; 647 } 648 } else { 649 Log.w(TAG, "Neither local nor remote playback available"); 650 return false; 651 } 652 } 653 playFallbackRingtone()654 private boolean playFallbackRingtone() { 655 int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); 656 if (mAudioManager.getStreamVolume(streamType) == 0) { 657 return false; 658 } 659 int ringtoneType = RingtoneManager.getDefaultType(mUri); 660 if (ringtoneType != -1 && 661 RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { 662 Log.w(TAG, "not playing fallback for " + mUri); 663 return false; 664 } 665 // Default ringtone, try fallback ringtone. 666 try { 667 AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( 668 com.android.internal.R.raw.fallbackring); 669 if (afd == null) { 670 Log.e(TAG, "Could not load fallback ringtone"); 671 return false; 672 } 673 mLocalPlayer = new MediaPlayer(); 674 if (afd.getDeclaredLength() < 0) { 675 mLocalPlayer.setDataSource(afd.getFileDescriptor()); 676 } else { 677 mLocalPlayer.setDataSource(afd.getFileDescriptor(), 678 afd.getStartOffset(), 679 afd.getDeclaredLength()); 680 } 681 mLocalPlayer.setAudioAttributes(mAudioAttributes); 682 synchronized (mPlaybackSettingsLock) { 683 applyPlaybackProperties_sync(); 684 } 685 if (mVolumeShaperConfig != null) { 686 mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); 687 } 688 mLocalPlayer.prepare(); 689 startLocalPlayer(); 690 afd.close(); 691 } catch (IOException ioe) { 692 destroyLocalPlayer(); 693 Log.e(TAG, "Failed to open fallback ringtone"); 694 return false; 695 } catch (NotFoundException nfe) { 696 Log.e(TAG, "Fallback ringtone does not exist"); 697 return false; 698 } 699 return true; 700 } 701 setTitle(String title)702 void setTitle(String title) { 703 mTitle = title; 704 } 705 706 @Override finalize()707 protected void finalize() { 708 if (mLocalPlayer != null) { 709 mLocalPlayer.release(); 710 } 711 } 712 713 class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { 714 @Override onCompletion(MediaPlayer mp)715 public void onCompletion(MediaPlayer mp) { 716 synchronized (sActiveRingtones) { 717 sActiveRingtones.remove(Ringtone.this); 718 } 719 mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle. 720 } 721 } 722 } 723