Loading src/com/android/server/telecom/AsyncRingtonePlayer.java +40 −22 Original line number Diff line number Diff line Loading @@ -18,9 +18,7 @@ package com.android.server.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.Ringtone; import android.media.RingtoneManager; import android.media.VolumeShaper; import android.net.Uri; import android.os.Handler; Loading @@ -32,6 +30,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.util.function.BiConsumer; import java.util.function.Supplier; /** * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be * used from the main thread. Loading @@ -57,13 +58,16 @@ public class AsyncRingtonePlayer { * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change * the volume of the ringtone as it plays. * * @param ringtone The {@link Ringtone}. * @param ringtoneSupplier The {@link Ringtone} factory. * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration). */ public void play(@NonNull Ringtone ringtone) { public void play(@NonNull Supplier<Ringtone> ringtoneSupplier, BiConsumer<Ringtone, Boolean> ringtoneConsumer) { Log.d(this, "Posting play."); SomeArgs args = SomeArgs.obtain(); args.arg1 = ringtone; args.arg2 = Log.createSubsession(); args.arg1 = ringtoneSupplier; args.arg2 = ringtoneConsumer; args.arg3 = Log.createSubsession(); postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); } Loading Loading @@ -122,20 +126,31 @@ public class AsyncRingtonePlayer { * Starts the actual playback of the ringtone. Executes on ringtone-thread. */ private void handlePlay(SomeArgs args) { Ringtone ringtone = (Ringtone) args.arg1; Session session = (Session) args.arg2; Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1; BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2; Session session = (Session) args.arg3; args.recycle(); Log.continueSession(session, "ARP.hP"); try { // don't bother with any of this if there is an EVENT_STOP waiting. // Don't bother with any of this if there is an EVENT_STOP waiting, but give the // consumer a chance to do anything no matter what. if (mHandler.hasMessages(EVENT_STOP)) { ringtoneConsumer.accept(null, /* stopped= */ true); return; } Ringtone ringtone = null; try { ringtone = ringtoneSupplier.get(); ThreadUtil.checkNotOnMainThread(); // setRingtone even if null - it also stops any current ringtone to be consistent // with the overall state. setRingtone(ringtone); if (mRingtone == null) { // The ringtoneConsumer can still vibrate at this stage. Log.w(this, "No ringtone was found bail out from playing."); return; } Uri uri = mRingtone.getUri(); String uriString = (uri != null ? uri.toSafeString() : ""); Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString); Loading @@ -146,6 +161,9 @@ public class AsyncRingtonePlayer { } mRingtone.play(); Log.i(this, "Play ringtone, looping."); } finally { ringtoneConsumer.accept(ringtone, /* stopped= */ false); } } finally { Log.cancelSubsession(session); } Loading src/com/android/server/telecom/Ringer.java +203 −185 Original line number Diff line number Diff line Loading @@ -51,6 +51,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import java.util.function.Supplier; /** * Controls the ringtone player. Loading Loading @@ -246,6 +248,9 @@ public class Ringer { } public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) { boolean deferBlockOnRingingFuture = false; // try-finally to ensure that the block on ringing future is always called. try { if (foregroundCall == null) { Log.wtf(this, "startRinging called with null foreground call."); return false; Loading @@ -253,16 +258,16 @@ public class Ringer { if (foregroundCall.getState() != CallState.RINGING && foregroundCall.getState() != CallState.SIMULATED_RINGING) { // Its possible for bluetooth to connect JUST as a call goes active, which would mean // the call would start ringing again. // It's possible for bluetooth to connect JUST as a call goes active, which would // mean the call would start ringing again. Log.i(this, "startRinging called for non-ringing foreground callid=%s", foregroundCall.getId()); return false; } // Use completable future to establish a timeout, not intent to make these work outside the // main thread asynchronously // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking. // Use completable future to establish a timeout, not intent to make these work outside // the main thread asynchronously // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached), new LoggedHandlerExecutor(getHandler(), "R.sR", null)); Loading @@ -278,7 +283,8 @@ public class Ringer { } if (attributes == null) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); return false; } Loading @@ -298,9 +304,6 @@ public class Ringer { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Work profile in quiet mode"); } if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return acquireAudioFocus; } Loading @@ -314,7 +317,8 @@ public class Ringer { AccessibilityManager.FLASH_REASON_CALL)); } // Determine if the settings and DND mode indicate that the vibrator can be used right now. // Determine if the settings and DND mode indicate that the vibrator can be used right // now. final boolean isVibratorEnabled = isVibratorEnabled(mContext, attributes.shouldRingForContact()); boolean shouldApplyRampingRinger = Loading @@ -338,11 +342,14 @@ public class Ringer { / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION); mVolumeShaperConfig = new VolumeShaper.Configuration.Builder() .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION) .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION) .setCurve( new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f}, new float[]{0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f}, new float[]{0.f, 0.f, 1.f}) .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .setInterpolatorType( VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .build(); if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) { useCustomVibrationEffect = true; Loading @@ -367,9 +374,6 @@ public class Ringer { Log.i(this, "Set ringtone as haptic only: " + isHapticOnly); } } else { if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE); return attributes.shouldAcquireAudioFocus(); // ringer not audible } Loading @@ -382,24 +386,40 @@ public class Ringer { Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled"); hapticChannelsMuted = true; } else if (hapticChannelsMuted) { Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s", Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s", isVibratorEnabled, mIsHapticPlaybackSupportedByDevice); } Ringtone ringtone; // Defer ringtone creation to the async player thread. Supplier<Ringtone> ringtoneSupplier; final boolean finalHapticChannelsMuted = hapticChannelsMuted; if (isHapticOnly) { if (hapticChannelsMuted) { Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play"); ringtone = null; Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play"); ringtoneSupplier = null; } else { ringtone = mRingtoneFactory.getHapticOnlyRingtone(); ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone; } } else { ringtone = mRingtoneFactory.getRingtone( foregroundCall, mVolumeShaperConfig, hapticChannelsMuted); ringtoneSupplier = () -> mRingtoneFactory.getRingtone( foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted); } // The vibration logic depends on the loaded ringtone, but we need to defer the ringtone // load to the async ringtone thread. Hence, we bundle up the final part of this method // for that thread to run after loading the ringtone. This logic is intended to run even // if the loaded ringtone is null. However if a stop event arrives before the ringtone // creation finishes, then this consumer can be skipped. final boolean finalUseCustomVibrationEffect = useCustomVibrationEffect; final RingerAttributes finalAttributes = attributes; BiConsumer<Ringtone, Boolean> vibrationLogic = (Ringtone ringtone, Boolean stopped) -> { try { if (stopped.booleanValue()) { return; // don't start vibration if the ringing is already abandoned. } final VibrationEffect vibrationEffect; if (useCustomVibrationEffect) { if (ringtone != null && finalUseCustomVibrationEffect) { if (DEBUG_RINGER) { Log.d(this, "Using ringtone defined vibration effect."); } Loading @@ -408,31 +428,32 @@ public class Ringer { vibrationEffect = mDefaultVibrationEffect; } if (ringtone == null) { Log.w(this, "No ringtone was found bail out from playing."); // No ringtone was found, try as a last resort to check if vibration can be performed. vibrateIfNeeded(/* isUsingAudioCoupledHaptics */ false, attributes, foregroundCall, boolean isUsingAudioCoupledHaptics = !finalHapticChannelsMuted && ringtone != null && ringtone.hasHapticChannels(); vibrateIfNeeded(isUsingAudioCoupledHaptics, finalAttributes, foregroundCall, vibrationEffect, isVibratorEnabled); } finally { // This is used to signal to tests that the async play() call has completed. if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return attributes.shouldAcquireAudioFocus(); } boolean isUsingAudioCoupledHaptics = !hapticChannelsMuted && ringtone.hasHapticChannels(); // There is a ringtone and we want to play it. mRingtonePlayer.play(ringtone); vibrateIfNeeded(isUsingAudioCoupledHaptics, attributes, foregroundCall, vibrationEffect, isVibratorEnabled); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } }; deferBlockOnRingingFuture = true; // Run in vibrationLogic. mRingtonePlayer.play(ringtoneSupplier, vibrationLogic); // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here // because until now is when we actually know if the ringtone loading worked. return attributes.shouldAcquireAudioFocus() || attributes.isRingerAudible(); return attributes.shouldAcquireAudioFocus() || (!isHapticOnly && attributes.isRingerAudible()); } finally { // This is used to signal to tests that the async play() call has completed. It can // be deferred into AsyncRingtonePlayer if (mBlockOnRingingFuture != null && !deferBlockOnRingingFuture) { mBlockOnRingingFuture.complete(null); } } } private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, RingerAttributes attributes, Loading Loading @@ -465,29 +486,26 @@ public class Ringer { } } private VibrationEffect getVibrationEffectForRingtone(Ringtone ringtone) { VibrationEffect effect = null; Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null; if (ringtoneUri != null) { private VibrationEffect getVibrationEffectForRingtone(@NonNull Ringtone ringtone) { Uri ringtoneUri = ringtone.getUri(); if (ringtoneUri == null) { return mDefaultVibrationEffect; } try { effect = mVibrationEffectProxy.get(ringtoneUri, mContext); VibrationEffect effect = mVibrationEffectProxy.get(ringtoneUri, mContext); if (effect == null) { Log.i(this, "did not find vibration effect, falling back to default vibration"); return mDefaultVibrationEffect; } return effect; } catch (IllegalArgumentException iae) { // Deep in the bowels of the VibrationEffect class it is possible for an // IllegalArgumentException to be thrown if there is an invalid URI specified in the // device config, or a content provider failure. Rather than crashing the Telecom // process we will just use the default vibration effect. Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect"); effect = null; } } if (effect == null) { effect = mDefaultVibrationEffect; return mDefaultVibrationEffect; } return effect; } public void startCallWaiting(Call call) { Loading src/com/android/server/telecom/RingtoneFactory.java +6 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,9 @@ public class RingtoneFactory { public Ringtone getRingtone(Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) { // Initializing ringtones on the main thread can deadlock ThreadUtil.checkNotOnMainThread(); AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted); // Use the default ringtone of the work profile if the contact is a work profile contact. Loading Loading @@ -116,7 +119,7 @@ public class RingtoneFactory { return ringtone; } public AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) { private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) { return new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) Loading @@ -127,6 +130,8 @@ public class RingtoneFactory { /** Returns a ringtone to be used when ringer is not audible for the incoming call. */ @Nullable public Ringtone getHapticOnlyRingtone() { // Initializing ringtones on the main thread can deadlock ThreadUtil.checkNotOnMainThread(); Uri ringtoneUri = Uri.parse("file://" + mContext.getString( com.android.internal.R.string.config_defaultRingtoneVibrationSound)); AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes( Loading Loading
src/com/android/server/telecom/AsyncRingtonePlayer.java +40 −22 Original line number Diff line number Diff line Loading @@ -18,9 +18,7 @@ package com.android.server.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.Ringtone; import android.media.RingtoneManager; import android.media.VolumeShaper; import android.net.Uri; import android.os.Handler; Loading @@ -32,6 +30,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.util.function.BiConsumer; import java.util.function.Supplier; /** * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be * used from the main thread. Loading @@ -57,13 +58,16 @@ public class AsyncRingtonePlayer { * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change * the volume of the ringtone as it plays. * * @param ringtone The {@link Ringtone}. * @param ringtoneSupplier The {@link Ringtone} factory. * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration). */ public void play(@NonNull Ringtone ringtone) { public void play(@NonNull Supplier<Ringtone> ringtoneSupplier, BiConsumer<Ringtone, Boolean> ringtoneConsumer) { Log.d(this, "Posting play."); SomeArgs args = SomeArgs.obtain(); args.arg1 = ringtone; args.arg2 = Log.createSubsession(); args.arg1 = ringtoneSupplier; args.arg2 = ringtoneConsumer; args.arg3 = Log.createSubsession(); postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); } Loading Loading @@ -122,20 +126,31 @@ public class AsyncRingtonePlayer { * Starts the actual playback of the ringtone. Executes on ringtone-thread. */ private void handlePlay(SomeArgs args) { Ringtone ringtone = (Ringtone) args.arg1; Session session = (Session) args.arg2; Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1; BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2; Session session = (Session) args.arg3; args.recycle(); Log.continueSession(session, "ARP.hP"); try { // don't bother with any of this if there is an EVENT_STOP waiting. // Don't bother with any of this if there is an EVENT_STOP waiting, but give the // consumer a chance to do anything no matter what. if (mHandler.hasMessages(EVENT_STOP)) { ringtoneConsumer.accept(null, /* stopped= */ true); return; } Ringtone ringtone = null; try { ringtone = ringtoneSupplier.get(); ThreadUtil.checkNotOnMainThread(); // setRingtone even if null - it also stops any current ringtone to be consistent // with the overall state. setRingtone(ringtone); if (mRingtone == null) { // The ringtoneConsumer can still vibrate at this stage. Log.w(this, "No ringtone was found bail out from playing."); return; } Uri uri = mRingtone.getUri(); String uriString = (uri != null ? uri.toSafeString() : ""); Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString); Loading @@ -146,6 +161,9 @@ public class AsyncRingtonePlayer { } mRingtone.play(); Log.i(this, "Play ringtone, looping."); } finally { ringtoneConsumer.accept(ringtone, /* stopped= */ false); } } finally { Log.cancelSubsession(session); } Loading
src/com/android/server/telecom/Ringer.java +203 −185 Original line number Diff line number Diff line Loading @@ -51,6 +51,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import java.util.function.Supplier; /** * Controls the ringtone player. Loading Loading @@ -246,6 +248,9 @@ public class Ringer { } public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) { boolean deferBlockOnRingingFuture = false; // try-finally to ensure that the block on ringing future is always called. try { if (foregroundCall == null) { Log.wtf(this, "startRinging called with null foreground call."); return false; Loading @@ -253,16 +258,16 @@ public class Ringer { if (foregroundCall.getState() != CallState.RINGING && foregroundCall.getState() != CallState.SIMULATED_RINGING) { // Its possible for bluetooth to connect JUST as a call goes active, which would mean // the call would start ringing again. // It's possible for bluetooth to connect JUST as a call goes active, which would // mean the call would start ringing again. Log.i(this, "startRinging called for non-ringing foreground callid=%s", foregroundCall.getId()); return false; } // Use completable future to establish a timeout, not intent to make these work outside the // main thread asynchronously // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking. // Use completable future to establish a timeout, not intent to make these work outside // the main thread asynchronously // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached), new LoggedHandlerExecutor(getHandler(), "R.sR", null)); Loading @@ -278,7 +283,8 @@ public class Ringer { } if (attributes == null) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); return false; } Loading @@ -298,9 +304,6 @@ public class Ringer { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Work profile in quiet mode"); } if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return acquireAudioFocus; } Loading @@ -314,7 +317,8 @@ public class Ringer { AccessibilityManager.FLASH_REASON_CALL)); } // Determine if the settings and DND mode indicate that the vibrator can be used right now. // Determine if the settings and DND mode indicate that the vibrator can be used right // now. final boolean isVibratorEnabled = isVibratorEnabled(mContext, attributes.shouldRingForContact()); boolean shouldApplyRampingRinger = Loading @@ -338,11 +342,14 @@ public class Ringer { / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION); mVolumeShaperConfig = new VolumeShaper.Configuration.Builder() .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION) .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION) .setCurve( new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f}, new float[]{0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f}, new float[]{0.f, 0.f, 1.f}) .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .setInterpolatorType( VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .build(); if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) { useCustomVibrationEffect = true; Loading @@ -367,9 +374,6 @@ public class Ringer { Log.i(this, "Set ringtone as haptic only: " + isHapticOnly); } } else { if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE); return attributes.shouldAcquireAudioFocus(); // ringer not audible } Loading @@ -382,24 +386,40 @@ public class Ringer { Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled"); hapticChannelsMuted = true; } else if (hapticChannelsMuted) { Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s", Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s", isVibratorEnabled, mIsHapticPlaybackSupportedByDevice); } Ringtone ringtone; // Defer ringtone creation to the async player thread. Supplier<Ringtone> ringtoneSupplier; final boolean finalHapticChannelsMuted = hapticChannelsMuted; if (isHapticOnly) { if (hapticChannelsMuted) { Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play"); ringtone = null; Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play"); ringtoneSupplier = null; } else { ringtone = mRingtoneFactory.getHapticOnlyRingtone(); ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone; } } else { ringtone = mRingtoneFactory.getRingtone( foregroundCall, mVolumeShaperConfig, hapticChannelsMuted); ringtoneSupplier = () -> mRingtoneFactory.getRingtone( foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted); } // The vibration logic depends on the loaded ringtone, but we need to defer the ringtone // load to the async ringtone thread. Hence, we bundle up the final part of this method // for that thread to run after loading the ringtone. This logic is intended to run even // if the loaded ringtone is null. However if a stop event arrives before the ringtone // creation finishes, then this consumer can be skipped. final boolean finalUseCustomVibrationEffect = useCustomVibrationEffect; final RingerAttributes finalAttributes = attributes; BiConsumer<Ringtone, Boolean> vibrationLogic = (Ringtone ringtone, Boolean stopped) -> { try { if (stopped.booleanValue()) { return; // don't start vibration if the ringing is already abandoned. } final VibrationEffect vibrationEffect; if (useCustomVibrationEffect) { if (ringtone != null && finalUseCustomVibrationEffect) { if (DEBUG_RINGER) { Log.d(this, "Using ringtone defined vibration effect."); } Loading @@ -408,31 +428,32 @@ public class Ringer { vibrationEffect = mDefaultVibrationEffect; } if (ringtone == null) { Log.w(this, "No ringtone was found bail out from playing."); // No ringtone was found, try as a last resort to check if vibration can be performed. vibrateIfNeeded(/* isUsingAudioCoupledHaptics */ false, attributes, foregroundCall, boolean isUsingAudioCoupledHaptics = !finalHapticChannelsMuted && ringtone != null && ringtone.hasHapticChannels(); vibrateIfNeeded(isUsingAudioCoupledHaptics, finalAttributes, foregroundCall, vibrationEffect, isVibratorEnabled); } finally { // This is used to signal to tests that the async play() call has completed. if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return attributes.shouldAcquireAudioFocus(); } boolean isUsingAudioCoupledHaptics = !hapticChannelsMuted && ringtone.hasHapticChannels(); // There is a ringtone and we want to play it. mRingtonePlayer.play(ringtone); vibrateIfNeeded(isUsingAudioCoupledHaptics, attributes, foregroundCall, vibrationEffect, isVibratorEnabled); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } }; deferBlockOnRingingFuture = true; // Run in vibrationLogic. mRingtonePlayer.play(ringtoneSupplier, vibrationLogic); // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here // because until now is when we actually know if the ringtone loading worked. return attributes.shouldAcquireAudioFocus() || attributes.isRingerAudible(); return attributes.shouldAcquireAudioFocus() || (!isHapticOnly && attributes.isRingerAudible()); } finally { // This is used to signal to tests that the async play() call has completed. It can // be deferred into AsyncRingtonePlayer if (mBlockOnRingingFuture != null && !deferBlockOnRingingFuture) { mBlockOnRingingFuture.complete(null); } } } private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, RingerAttributes attributes, Loading Loading @@ -465,29 +486,26 @@ public class Ringer { } } private VibrationEffect getVibrationEffectForRingtone(Ringtone ringtone) { VibrationEffect effect = null; Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null; if (ringtoneUri != null) { private VibrationEffect getVibrationEffectForRingtone(@NonNull Ringtone ringtone) { Uri ringtoneUri = ringtone.getUri(); if (ringtoneUri == null) { return mDefaultVibrationEffect; } try { effect = mVibrationEffectProxy.get(ringtoneUri, mContext); VibrationEffect effect = mVibrationEffectProxy.get(ringtoneUri, mContext); if (effect == null) { Log.i(this, "did not find vibration effect, falling back to default vibration"); return mDefaultVibrationEffect; } return effect; } catch (IllegalArgumentException iae) { // Deep in the bowels of the VibrationEffect class it is possible for an // IllegalArgumentException to be thrown if there is an invalid URI specified in the // device config, or a content provider failure. Rather than crashing the Telecom // process we will just use the default vibration effect. Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect"); effect = null; } } if (effect == null) { effect = mDefaultVibrationEffect; return mDefaultVibrationEffect; } return effect; } public void startCallWaiting(Call call) { Loading
src/com/android/server/telecom/RingtoneFactory.java +6 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,9 @@ public class RingtoneFactory { public Ringtone getRingtone(Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) { // Initializing ringtones on the main thread can deadlock ThreadUtil.checkNotOnMainThread(); AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted); // Use the default ringtone of the work profile if the contact is a work profile contact. Loading Loading @@ -116,7 +119,7 @@ public class RingtoneFactory { return ringtone; } public AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) { private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) { return new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) Loading @@ -127,6 +130,8 @@ public class RingtoneFactory { /** Returns a ringtone to be used when ringer is not audible for the incoming call. */ @Nullable public Ringtone getHapticOnlyRingtone() { // Initializing ringtones on the main thread can deadlock ThreadUtil.checkNotOnMainThread(); Uri ringtoneUri = Uri.parse("file://" + mContext.getString( com.android.internal.R.string.config_defaultRingtoneVibrationSound)); AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes( Loading