• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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