Loading core/java/android/os/VibrationEffect.java +26 −18 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.Log; import android.util.MathUtils; import com.android.internal.util.Preconditions; Loading @@ -52,6 +53,7 @@ import java.util.Objects; * <p>These effects may be any number of things, from single shot vibrations to complex waveforms. */ public abstract class VibrationEffect implements Parcelable { private static final String TAG = "VibrationEffect"; // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; // If a vibration is playing for longer than 1s, it's probably not haptic feedback Loading Loading @@ -394,12 +396,13 @@ public abstract class VibrationEffect implements Parcelable { return null; } try { final ContentResolver cr = context.getContentResolver(); Uri uncanonicalUri = cr.uncanonicalize(uri); if (uncanonicalUri == null) { // If we already had an uncanonical URI, it's possible we'll get null back here. In // this case, just use the URI as passed in since it wasn't canonicalized in the first // place. // this case, just use the URI as passed in since it wasn't canonicalized in the // first place. uncanonicalUri = uri; } Loading @@ -415,6 +418,11 @@ public abstract class VibrationEffect implements Parcelable { return get(RINGTONES[i]); } } } catch (Exception e) { // Don't give unexpected exceptions to callers if the Uri's ContentProvider is // misbehaving - it's very unlikely to be mapped in that case anyway. Log.e(TAG, "Exception getting default vibration for Uri " + uri, e); } return null; } Loading media/java/android/media/IRingtonePlayer.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.media.VolumeShaper; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.os.VibrationEffect; /** * @hide Loading @@ -29,7 +30,8 @@ interface IRingtonePlayer { /** Used for Ringtone.java playback */ @UnsupportedAppUsage oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping); oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa, oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa, boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve, float volume, boolean looping, boolean hapticGeneratorEnabled, in @nullable VolumeShaper.Configuration volumeShaperConfig); oneway void stop(IBinder token); Loading media/java/android/media/LocalRingtonePlayer.java +113 −35 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ import android.content.res.AssetFileDescriptor; import android.media.audiofx.HapticGenerator; import android.net.Uri; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import java.io.IOException; Loading @@ -36,21 +39,27 @@ import java.util.Objects; public class LocalRingtonePlayer implements Ringtone.RingtonePlayer, MediaPlayer.OnCompletionListener { private static final String TAG = "LocalRingtonePlayer"; private static final int VIBRATION_LOOP_DELAY_MS = 200; // keep references on active Ringtones until stopped or completion listener called. private static final ArrayList<LocalRingtonePlayer> sActiveRingtones = new ArrayList<>(); private static final ArrayList<LocalRingtonePlayer> sActiveMediaPlayers = new ArrayList<>(); private final MediaPlayer mMediaPlayer; private final AudioAttributes mAudioAttributes; private final VibrationAttributes mVibrationAttributes; private final Ringtone.Injectables mInjectables; private final AudioManager mAudioManager; private final VolumeShaper mVolumeShaper; private final Vibrator mVibrator; private final VibrationEffect mVibrationEffect; private HapticGenerator mHapticGenerator; private boolean mStartedVibration; private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer, @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables, @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator, @Nullable VolumeShaper volumeShaper) { @Nullable VolumeShaper volumeShaper, @NonNull Vibrator vibrator, @Nullable VibrationEffect vibrationEffect) { Objects.requireNonNull(mediaPlayer); Objects.requireNonNull(audioAttributes); Objects.requireNonNull(injectables); Loading @@ -60,7 +69,11 @@ public class LocalRingtonePlayer mInjectables = injectables; mAudioManager = audioManager; mVolumeShaper = volumeShaper; mVibrator = vibrator; mVibrationEffect = vibrationEffect; mHapticGenerator = hapticGenerator; mVibrationAttributes = (mVibrationEffect == null) ? null : new VibrationAttributes.Builder(audioAttributes).build(); } /** Loading @@ -69,8 +82,9 @@ public class LocalRingtonePlayer */ @Nullable static LocalRingtonePlayer create(@NonNull Context context, @NonNull AudioManager audioManager, @NonNull Uri soundUri, @NonNull AudioManager audioManager, @NonNull Vibrator vibrator, @NonNull Uri soundUri, @NonNull AudioAttributes audioAttributes, @Nullable VibrationEffect vibrationEffect, @NonNull Ringtone.Injectables injectables, @Nullable VolumeShaper.Configuration volumeShaperConfig, @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled, Loading @@ -89,15 +103,26 @@ public class LocalRingtonePlayer mediaPlayer.setVolume(initialVolume); if (initialHapticGeneratorEnabled) { hapticGenerator = injectables.createHapticGenerator(mediaPlayer); if (hapticGenerator != null) { // In practise, this should always be non-null because the initial value is // not true unless it's available. hapticGenerator.setEnabled(true); vibrationEffect = null; // Don't play the VibrationEffect. } } VolumeShaper volumeShaper = null; if (volumeShaperConfig != null) { volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig); } mediaPlayer.prepare(); if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) { if (injectables.hasHapticChannels(mediaPlayer)) { // Don't play the Vibration effect if the URI has haptic channels. vibrationEffect = null; } } return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager, hapticGenerator, volumeShaper); hapticGenerator, volumeShaper, vibrator, vibrationEffect); } catch (SecurityException | IOException e) { if (hapticGenerator != null) { hapticGenerator.release(); Loading @@ -116,8 +141,10 @@ public class LocalRingtonePlayer */ @Nullable static LocalRingtonePlayer createForFallback( @NonNull AudioManager audioManager, @NonNull AssetFileDescriptor afd, @NonNull AudioManager audioManager, @NonNull Vibrator vibrator, @NonNull AssetFileDescriptor afd, @NonNull AudioAttributes audioAttributes, @Nullable VibrationEffect vibrationEffect, @NonNull Ringtone.Injectables injectables, @Nullable VolumeShaper.Configuration volumeShaperConfig, @Nullable AudioDeviceInfo preferredDevice, Loading Loading @@ -146,10 +173,17 @@ public class LocalRingtonePlayer volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig); } mediaPlayer.prepare(); if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) { if (injectables.hasHapticChannels(mediaPlayer)) { // Don't play the Vibration effect if the URI has haptic channels. vibrationEffect = null; } } return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager, /* hapticGenerator= */ null, volumeShaper); /* hapticGenerator= */ null, volumeShaper, vibrator, vibrationEffect); } catch (SecurityException | IOException e) { Log.e(TAG, "Failed to open fallback ringtone"); // TODO: vibration-effect-only / no-sound LocalRingtonePlayer. mediaPlayer.release(); return null; } finally { Loading @@ -163,10 +197,14 @@ public class LocalRingtonePlayer // (typically because ringer mode is vibrate). if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) == 0 && (mAudioAttributes.areHapticChannelsMuted() || !hasHapticChannels())) { maybeStartVibration(); return true; // Successfully played while muted. } synchronized (sActiveRingtones) { sActiveRingtones.add(this); synchronized (sActiveMediaPlayers) { // We keep-alive when a mediaplayer is active, since its finalizer would stop the // ringtone. This isn't necessary for vibrations in the vibrator service // (i.e. maybeStartVibration in the muted case, above). sActiveMediaPlayers.add(this); } mMediaPlayer.setOnCompletionListener(this); Loading @@ -174,9 +212,41 @@ public class LocalRingtonePlayer if (mVolumeShaper != null) { mVolumeShaper.apply(VolumeShaper.Operation.PLAY); } maybeStartVibration(); return true; } private void maybeStartVibration() { if (mVibrationEffect != null && !mStartedVibration) { boolean isLooping = mMediaPlayer.isLooping(); try { // Adjust the vibration effect to loop. VibrationEffect loopAdjustedEffect = mVibrationEffect.applyRepeatingIndefinitely( isLooping, VIBRATION_LOOP_DELAY_MS); mVibrator.vibrate(loopAdjustedEffect, mVibrationAttributes); mStartedVibration = true; } catch (Exception e) { // Catch exceptions widely, because we don't want to "leak" looping sounds or // vibrations if something goes wrong. Log.e(TAG, "Problem starting " + (isLooping ? "looping " : "") + "vibration " + "for ringtone: " + mVibrationEffect, e); } } } private void stopVibration() { if (mVibrationEffect != null && mStartedVibration) { try { mVibrator.cancel(mVibrationAttributes.getUsage()); mStartedVibration = false; } catch (Exception e) { // Catch exceptions widely, because we don't want to "leak" looping sounds or // vibrations if something goes wrong. Log.e(TAG, "Problem stopping vibration for ringtone", e); } } } @Override public boolean isPlaying() { return mMediaPlayer.isPlaying(); Loading @@ -184,9 +254,13 @@ public class LocalRingtonePlayer @Override public void stopAndRelease() { synchronized (sActiveRingtones) { sActiveRingtones.remove(this); synchronized (sActiveMediaPlayers) { sActiveMediaPlayers.remove(this); } try { mMediaPlayer.stop(); } finally { stopVibration(); // Won't throw: catches exceptions. if (mHapticGenerator != null) { mHapticGenerator.release(); } Loading @@ -194,6 +268,7 @@ public class LocalRingtonePlayer mMediaPlayer.reset(); mMediaPlayer.release(); } } @Override public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { Loading @@ -202,11 +277,29 @@ public class LocalRingtonePlayer @Override public void setLooping(boolean looping) { boolean wasLooping = mMediaPlayer.isLooping(); if (wasLooping == looping) { return; } mMediaPlayer.setLooping(looping); // If transitioning from looping to not-looping during play, then cancel the vibration. if (mVibrationEffect != null && mMediaPlayer.isPlaying()) { if (wasLooping) { stopVibration(); } else { // Won't restart the vibration to be looping if it was already started. maybeStartVibration(); } } } @Override public void setHapticGeneratorEnabled(boolean enabled) { if (mVibrationEffect != null) { // Ignore haptic generator changes if a vibration effect is present. The decision to // use one or the other happens before this object is constructed. return; } if (enabled && mHapticGenerator == null) { mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer); } Loading @@ -220,30 +313,15 @@ public class LocalRingtonePlayer mMediaPlayer.setVolume(volume); } /** * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. * @hide */ @Override public boolean hasHapticChannels() { // FIXME: support remote player, or internalize haptic channels support and remove entirely. try { Trace.beginSection("LocalRingtonePlayer.hasHapticChannels"); for (MediaPlayer.TrackInfo trackInfo : mMediaPlayer.getTrackInfo()) { if (trackInfo.hasHapticChannels()) { return true; } } } finally { Trace.endSection(); } return false; return mInjectables.hasHapticChannels(mMediaPlayer); } @Override public void onCompletion(MediaPlayer mp) { synchronized (sActiveRingtones) { sActiveRingtones.remove(this); synchronized (sActiveMediaPlayers) { sActiveMediaPlayers.remove(this); } mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle. } Loading media/java/android/media/Ringtone.java +221 −33 File changed.Preview size limit exceeded, changes collapsed. Show changes media/java/android/media/RingtoneManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -709,6 +709,7 @@ public class RingtoneManager { return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes) .setUri(ringtoneUri) .setVolumeShaperConfig(volumeShaperConfig) .setUseExactAudioAttributes(true) // May be using audio-coupled via attrs .build(); } Loading Loading
core/java/android/os/VibrationEffect.java +26 −18 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.Log; import android.util.MathUtils; import com.android.internal.util.Preconditions; Loading @@ -52,6 +53,7 @@ import java.util.Objects; * <p>These effects may be any number of things, from single shot vibrations to complex waveforms. */ public abstract class VibrationEffect implements Parcelable { private static final String TAG = "VibrationEffect"; // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; // If a vibration is playing for longer than 1s, it's probably not haptic feedback Loading Loading @@ -394,12 +396,13 @@ public abstract class VibrationEffect implements Parcelable { return null; } try { final ContentResolver cr = context.getContentResolver(); Uri uncanonicalUri = cr.uncanonicalize(uri); if (uncanonicalUri == null) { // If we already had an uncanonical URI, it's possible we'll get null back here. In // this case, just use the URI as passed in since it wasn't canonicalized in the first // place. // this case, just use the URI as passed in since it wasn't canonicalized in the // first place. uncanonicalUri = uri; } Loading @@ -415,6 +418,11 @@ public abstract class VibrationEffect implements Parcelable { return get(RINGTONES[i]); } } } catch (Exception e) { // Don't give unexpected exceptions to callers if the Uri's ContentProvider is // misbehaving - it's very unlikely to be mapped in that case anyway. Log.e(TAG, "Exception getting default vibration for Uri " + uri, e); } return null; } Loading
media/java/android/media/IRingtonePlayer.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.media.VolumeShaper; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.os.VibrationEffect; /** * @hide Loading @@ -29,7 +30,8 @@ interface IRingtonePlayer { /** Used for Ringtone.java playback */ @UnsupportedAppUsage oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping); oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa, oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa, boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve, float volume, boolean looping, boolean hapticGeneratorEnabled, in @nullable VolumeShaper.Configuration volumeShaperConfig); oneway void stop(IBinder token); Loading
media/java/android/media/LocalRingtonePlayer.java +113 −35 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ import android.content.res.AssetFileDescriptor; import android.media.audiofx.HapticGenerator; import android.net.Uri; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import java.io.IOException; Loading @@ -36,21 +39,27 @@ import java.util.Objects; public class LocalRingtonePlayer implements Ringtone.RingtonePlayer, MediaPlayer.OnCompletionListener { private static final String TAG = "LocalRingtonePlayer"; private static final int VIBRATION_LOOP_DELAY_MS = 200; // keep references on active Ringtones until stopped or completion listener called. private static final ArrayList<LocalRingtonePlayer> sActiveRingtones = new ArrayList<>(); private static final ArrayList<LocalRingtonePlayer> sActiveMediaPlayers = new ArrayList<>(); private final MediaPlayer mMediaPlayer; private final AudioAttributes mAudioAttributes; private final VibrationAttributes mVibrationAttributes; private final Ringtone.Injectables mInjectables; private final AudioManager mAudioManager; private final VolumeShaper mVolumeShaper; private final Vibrator mVibrator; private final VibrationEffect mVibrationEffect; private HapticGenerator mHapticGenerator; private boolean mStartedVibration; private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer, @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables, @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator, @Nullable VolumeShaper volumeShaper) { @Nullable VolumeShaper volumeShaper, @NonNull Vibrator vibrator, @Nullable VibrationEffect vibrationEffect) { Objects.requireNonNull(mediaPlayer); Objects.requireNonNull(audioAttributes); Objects.requireNonNull(injectables); Loading @@ -60,7 +69,11 @@ public class LocalRingtonePlayer mInjectables = injectables; mAudioManager = audioManager; mVolumeShaper = volumeShaper; mVibrator = vibrator; mVibrationEffect = vibrationEffect; mHapticGenerator = hapticGenerator; mVibrationAttributes = (mVibrationEffect == null) ? null : new VibrationAttributes.Builder(audioAttributes).build(); } /** Loading @@ -69,8 +82,9 @@ public class LocalRingtonePlayer */ @Nullable static LocalRingtonePlayer create(@NonNull Context context, @NonNull AudioManager audioManager, @NonNull Uri soundUri, @NonNull AudioManager audioManager, @NonNull Vibrator vibrator, @NonNull Uri soundUri, @NonNull AudioAttributes audioAttributes, @Nullable VibrationEffect vibrationEffect, @NonNull Ringtone.Injectables injectables, @Nullable VolumeShaper.Configuration volumeShaperConfig, @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled, Loading @@ -89,15 +103,26 @@ public class LocalRingtonePlayer mediaPlayer.setVolume(initialVolume); if (initialHapticGeneratorEnabled) { hapticGenerator = injectables.createHapticGenerator(mediaPlayer); if (hapticGenerator != null) { // In practise, this should always be non-null because the initial value is // not true unless it's available. hapticGenerator.setEnabled(true); vibrationEffect = null; // Don't play the VibrationEffect. } } VolumeShaper volumeShaper = null; if (volumeShaperConfig != null) { volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig); } mediaPlayer.prepare(); if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) { if (injectables.hasHapticChannels(mediaPlayer)) { // Don't play the Vibration effect if the URI has haptic channels. vibrationEffect = null; } } return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager, hapticGenerator, volumeShaper); hapticGenerator, volumeShaper, vibrator, vibrationEffect); } catch (SecurityException | IOException e) { if (hapticGenerator != null) { hapticGenerator.release(); Loading @@ -116,8 +141,10 @@ public class LocalRingtonePlayer */ @Nullable static LocalRingtonePlayer createForFallback( @NonNull AudioManager audioManager, @NonNull AssetFileDescriptor afd, @NonNull AudioManager audioManager, @NonNull Vibrator vibrator, @NonNull AssetFileDescriptor afd, @NonNull AudioAttributes audioAttributes, @Nullable VibrationEffect vibrationEffect, @NonNull Ringtone.Injectables injectables, @Nullable VolumeShaper.Configuration volumeShaperConfig, @Nullable AudioDeviceInfo preferredDevice, Loading Loading @@ -146,10 +173,17 @@ public class LocalRingtonePlayer volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig); } mediaPlayer.prepare(); if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) { if (injectables.hasHapticChannels(mediaPlayer)) { // Don't play the Vibration effect if the URI has haptic channels. vibrationEffect = null; } } return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager, /* hapticGenerator= */ null, volumeShaper); /* hapticGenerator= */ null, volumeShaper, vibrator, vibrationEffect); } catch (SecurityException | IOException e) { Log.e(TAG, "Failed to open fallback ringtone"); // TODO: vibration-effect-only / no-sound LocalRingtonePlayer. mediaPlayer.release(); return null; } finally { Loading @@ -163,10 +197,14 @@ public class LocalRingtonePlayer // (typically because ringer mode is vibrate). if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) == 0 && (mAudioAttributes.areHapticChannelsMuted() || !hasHapticChannels())) { maybeStartVibration(); return true; // Successfully played while muted. } synchronized (sActiveRingtones) { sActiveRingtones.add(this); synchronized (sActiveMediaPlayers) { // We keep-alive when a mediaplayer is active, since its finalizer would stop the // ringtone. This isn't necessary for vibrations in the vibrator service // (i.e. maybeStartVibration in the muted case, above). sActiveMediaPlayers.add(this); } mMediaPlayer.setOnCompletionListener(this); Loading @@ -174,9 +212,41 @@ public class LocalRingtonePlayer if (mVolumeShaper != null) { mVolumeShaper.apply(VolumeShaper.Operation.PLAY); } maybeStartVibration(); return true; } private void maybeStartVibration() { if (mVibrationEffect != null && !mStartedVibration) { boolean isLooping = mMediaPlayer.isLooping(); try { // Adjust the vibration effect to loop. VibrationEffect loopAdjustedEffect = mVibrationEffect.applyRepeatingIndefinitely( isLooping, VIBRATION_LOOP_DELAY_MS); mVibrator.vibrate(loopAdjustedEffect, mVibrationAttributes); mStartedVibration = true; } catch (Exception e) { // Catch exceptions widely, because we don't want to "leak" looping sounds or // vibrations if something goes wrong. Log.e(TAG, "Problem starting " + (isLooping ? "looping " : "") + "vibration " + "for ringtone: " + mVibrationEffect, e); } } } private void stopVibration() { if (mVibrationEffect != null && mStartedVibration) { try { mVibrator.cancel(mVibrationAttributes.getUsage()); mStartedVibration = false; } catch (Exception e) { // Catch exceptions widely, because we don't want to "leak" looping sounds or // vibrations if something goes wrong. Log.e(TAG, "Problem stopping vibration for ringtone", e); } } } @Override public boolean isPlaying() { return mMediaPlayer.isPlaying(); Loading @@ -184,9 +254,13 @@ public class LocalRingtonePlayer @Override public void stopAndRelease() { synchronized (sActiveRingtones) { sActiveRingtones.remove(this); synchronized (sActiveMediaPlayers) { sActiveMediaPlayers.remove(this); } try { mMediaPlayer.stop(); } finally { stopVibration(); // Won't throw: catches exceptions. if (mHapticGenerator != null) { mHapticGenerator.release(); } Loading @@ -194,6 +268,7 @@ public class LocalRingtonePlayer mMediaPlayer.reset(); mMediaPlayer.release(); } } @Override public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { Loading @@ -202,11 +277,29 @@ public class LocalRingtonePlayer @Override public void setLooping(boolean looping) { boolean wasLooping = mMediaPlayer.isLooping(); if (wasLooping == looping) { return; } mMediaPlayer.setLooping(looping); // If transitioning from looping to not-looping during play, then cancel the vibration. if (mVibrationEffect != null && mMediaPlayer.isPlaying()) { if (wasLooping) { stopVibration(); } else { // Won't restart the vibration to be looping if it was already started. maybeStartVibration(); } } } @Override public void setHapticGeneratorEnabled(boolean enabled) { if (mVibrationEffect != null) { // Ignore haptic generator changes if a vibration effect is present. The decision to // use one or the other happens before this object is constructed. return; } if (enabled && mHapticGenerator == null) { mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer); } Loading @@ -220,30 +313,15 @@ public class LocalRingtonePlayer mMediaPlayer.setVolume(volume); } /** * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. * @hide */ @Override public boolean hasHapticChannels() { // FIXME: support remote player, or internalize haptic channels support and remove entirely. try { Trace.beginSection("LocalRingtonePlayer.hasHapticChannels"); for (MediaPlayer.TrackInfo trackInfo : mMediaPlayer.getTrackInfo()) { if (trackInfo.hasHapticChannels()) { return true; } } } finally { Trace.endSection(); } return false; return mInjectables.hasHapticChannels(mMediaPlayer); } @Override public void onCompletion(MediaPlayer mp) { synchronized (sActiveRingtones) { sActiveRingtones.remove(this); synchronized (sActiveMediaPlayers) { sActiveMediaPlayers.remove(this); } mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle. } Loading
media/java/android/media/Ringtone.java +221 −33 File changed.Preview size limit exceeded, changes collapsed. Show changes
media/java/android/media/RingtoneManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -709,6 +709,7 @@ public class RingtoneManager { return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes) .setUri(ringtoneUri) .setVolumeShaperConfig(volumeShaperConfig) .setUseExactAudioAttributes(true) // May be using audio-coupled via attrs .build(); } Loading