Loading src/com/android/server/telecom/AsyncRingtonePlayer.java +61 −9 Original line number Diff line number Diff line Loading @@ -16,7 +16,9 @@ package com.android.server.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.Ringtone; import android.media.VolumeShaper; import android.net.Uri; Loading @@ -29,6 +31,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.util.concurrent.CompletableFuture; /** * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be * used from the main thread. Loading @@ -49,6 +53,12 @@ public class AsyncRingtonePlayer { /** The current ringtone. Only used by the ringtone thread. */ private Ringtone mRingtone; /** * CompletableFuture which signals a caller when we know whether a ringtone will play haptics * or not. */ private CompletableFuture<Boolean> mHapticsFuture = null; /** * Determines if the {@link AsyncRingtonePlayer} should pause between repeats of the ringtone. * When {@code true}, the system will check if the ringtone has stopped every Loading @@ -69,20 +79,36 @@ public class AsyncRingtonePlayer { mShouldPauseBetweenRepeat = shouldPauseBetweenRepeat; } /** Plays the ringtone with ramping ringer if required. */ public void play(RingtoneFactory factory, Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig) { /** * Plays the appropriate ringtone for the specified call. * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change * the volume of the ringtone as it plays. * * @param factory The {@link RingtoneFactory}. * @param incomingCall The ringing {@link Call}. * @param volumeShaperConfig An optional {@link VolumeShaper.Configuration} which is applied to * the ringtone to change its volume while it rings. * @param isVibrationEnabled {@code true} if the settings and DND configuration of the device * is such that the vibrator should be used, {@code false} otherwise. * @return A {@link CompletableFuture} which on completion indicates whether or not the ringtone * has a haptic track. {@code True} indicates that a haptic track is present on the * ringtone; in this case the default vibration in {@link Ringer} should not be played. * {@code False} indicates that a haptic track is NOT present on the ringtone; * in this case the default vibration in {@link Ringer} should be trigger if needed. */ public @NonNull CompletableFuture<Boolean> play(RingtoneFactory factory, Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean isVibrationEnabled) { Log.d(this, "Posting play."); if (mHapticsFuture == null) { mHapticsFuture = new CompletableFuture<>(); } SomeArgs args = SomeArgs.obtain(); args.arg1 = factory; args.arg2 = incomingCall; args.arg3 = volumeShaperConfig; args.arg4 = isVibrationEnabled; postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); } /** Plays the ringtone. */ public void play(RingtoneFactory factory, Call incomingCall) { play(factory, incomingCall, null); return mHapticsFuture; } /** Stops playing the ringtone. */ Loading Loading @@ -146,9 +172,12 @@ public class AsyncRingtonePlayer { RingtoneFactory factory = (RingtoneFactory) args.arg1; Call incomingCall = (Call) args.arg2; VolumeShaper.Configuration volumeShaperConfig = (VolumeShaper.Configuration) args.arg3; boolean isVibrationEnabled = (boolean) args.arg4; args.recycle(); // don't bother with any of this if there is an EVENT_STOP waiting. if (mHandler.hasMessages(EVENT_STOP)) { mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } Loading @@ -156,11 +185,13 @@ public class AsyncRingtonePlayer { // anything. if(Uri.EMPTY.equals(incomingCall.getRingtone())) { mRingtone = null; mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } ThreadUtil.checkNotOnMainThread(); Log.i(this, "Play ringtone."); Log.i(this, "handlePlay: Play ringtone."); if (mRingtone == null) { mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig); Loading @@ -170,8 +201,29 @@ public class AsyncRingtonePlayer { ringtoneUri.toSafeString(); Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " + "factory. Skipping ringing. Uri was: " + ringtoneUriString); mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } // With the ringtone to play now known, we can determine if it has haptic channels or // not; we will complete the haptics future so the default vibration code in Ringer // can know whether to trigger the vibrator. if (mHapticsFuture != null && !mHapticsFuture.isDone()) { boolean hasHaptics = factory.hasHapticChannels(mRingtone); Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics, isVibrationEnabled); if (!isVibrationEnabled && hasHaptics) { Log.i(this, "handlePlay: muting haptic channel"); mRingtone.setAudioAttributes( new AudioAttributes.Builder() .setMuteHapticChannels(true) .build()); } mHapticsFuture.complete(hasHaptics); mHapticsFuture = null; } } if (mShouldPauseBetweenRepeat) { Loading src/com/android/server/telecom/Ringer.java +68 −11 Original line number Diff line number Diff line Loading @@ -32,11 +32,12 @@ import android.os.Bundle; import android.os.Vibrator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; /** Loading Loading @@ -138,6 +139,12 @@ public class Ringer { private final Vibrator mVibrator; private final InCallController mInCallController; private final VibrationEffectProxy mVibrationEffectProxy; private final boolean mIsHapticPlaybackSupportedByDevice; /** * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}. */ private CompletableFuture<Void> mBlockOnRingingFuture = null; private InCallTonePlayer mCallWaitingPlayer; private RingtoneFactory mRingtoneFactory; Loading Loading @@ -185,6 +192,14 @@ public class Ringer { mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN, PULSE_AMPLITUDE, REPEAT_VIBRATION_AT); } mIsHapticPlaybackSupportedByDevice = mSystemSettingsUtil.isHapticPlaybackSupported(mContext); } @VisibleForTesting public void setBlockOnRingingFuture(CompletableFuture<Void> future) { mBlockOnRingingFuture = future; } public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) { Loading Loading @@ -229,12 +244,18 @@ public class Ringer { "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, isSilentRingingRequested); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return shouldAcquireAudioFocus; } stopCallWaiting(); VibrationEffect effect; CompletableFuture<Boolean> hapticsFuture = null; // Determine if the settings and DND mode indicate that the vibrator can be used right now. boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall); if (isRingerAudible) { mRingingCall = foregroundCall; Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER); Loading Loading @@ -277,9 +298,12 @@ public class Ringer { .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .build(); } mRingtonePlayer.play(mRingtoneFactory, foregroundCall, mVolumeShaperConfig); hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, mVolumeShaperConfig, isVibratorEnabled); } else { mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null); // Ramping ringtone is not enabled. hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null, isVibratorEnabled); effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall); } } else { Loading @@ -291,23 +315,56 @@ public class Ringer { effect = mDefaultVibrationEffect; } if (shouldVibrate(mContext, foregroundCall) if (hapticsFuture != null) { CompletableFuture<Void> vibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> { if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) { Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b", isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice); maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, isRingerAudible); } else { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics"); } }); if (mBlockOnRingingFuture != null) { vibrateFuture.thenCompose( v -> { mBlockOnRingingFuture.complete(null); return null; }); } } else { if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } Log.w(this, "startRinging: No haptics future; fallback to default behavior"); maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, isRingerAudible); } return shouldAcquireAudioFocus; } private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact, VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) { if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) { if (mSystemSettingsUtil.applyRampingRinger(mContext) && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig() && isRingerAudible) { Log.i(this, "start vibration for ramping ringer."); mVibrator.vibrate(effect); mIsVibrating = true; } else { Log.i(this, "start normal vibration."); mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES); } mIsVibrating = true; } } else if (mIsVibrating) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating"); } return shouldAcquireAudioFocus; } private VibrationEffect createRampingRingerVibrationEffect(int vibrationSeconds) { Loading Loading @@ -443,7 +500,7 @@ public class Ringer { } } private boolean shouldVibrate(Context context, Call call) { private boolean isVibratorEnabled(Context context, Call call) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerModeInternal(); boolean shouldVibrate; Loading src/com/android/server/telecom/RingtoneFactory.java +11 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,17 @@ public class RingtoneFactory { mCallsManager = callsManager; } /** * Determines if a ringtone has haptic channels. * @param ringtone The ringtone URI. * @return {@code true} if there is a haptic channel, {@code false} otherwise. */ public boolean hasHapticChannels(Ringtone ringtone) { boolean hasHapticChannels = RingtoneManager.hasHapticChannels(ringtone.getUri()); Log.i(this, "hasHapticChannels %s -> %b", ringtone.getUri(), hasHapticChannels); return hasHapticChannels; } public Ringtone getRingtone(Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig) { // Use the default ringtone of the work profile if the contact is a work profile contact. Loading src/com/android/server/telecom/SystemSettingsUtil.java +5 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.content.Context; import android.media.AudioManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.telecom.Log; Loading Loading @@ -78,5 +79,9 @@ public class SystemSettingsUtil { return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_VIBRATION_DURATION, 0); } public boolean isHapticPlaybackSupported(Context context) { return context.getSystemService(AudioManager.class).isHapticPlaybackSupported(); } } tests/src/com/android/server/telecom/tests/RingerTest.java +88 −22 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/server/telecom/AsyncRingtonePlayer.java +61 −9 Original line number Diff line number Diff line Loading @@ -16,7 +16,9 @@ package com.android.server.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.Ringtone; import android.media.VolumeShaper; import android.net.Uri; Loading @@ -29,6 +31,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.util.concurrent.CompletableFuture; /** * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be * used from the main thread. Loading @@ -49,6 +53,12 @@ public class AsyncRingtonePlayer { /** The current ringtone. Only used by the ringtone thread. */ private Ringtone mRingtone; /** * CompletableFuture which signals a caller when we know whether a ringtone will play haptics * or not. */ private CompletableFuture<Boolean> mHapticsFuture = null; /** * Determines if the {@link AsyncRingtonePlayer} should pause between repeats of the ringtone. * When {@code true}, the system will check if the ringtone has stopped every Loading @@ -69,20 +79,36 @@ public class AsyncRingtonePlayer { mShouldPauseBetweenRepeat = shouldPauseBetweenRepeat; } /** Plays the ringtone with ramping ringer if required. */ public void play(RingtoneFactory factory, Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig) { /** * Plays the appropriate ringtone for the specified call. * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change * the volume of the ringtone as it plays. * * @param factory The {@link RingtoneFactory}. * @param incomingCall The ringing {@link Call}. * @param volumeShaperConfig An optional {@link VolumeShaper.Configuration} which is applied to * the ringtone to change its volume while it rings. * @param isVibrationEnabled {@code true} if the settings and DND configuration of the device * is such that the vibrator should be used, {@code false} otherwise. * @return A {@link CompletableFuture} which on completion indicates whether or not the ringtone * has a haptic track. {@code True} indicates that a haptic track is present on the * ringtone; in this case the default vibration in {@link Ringer} should not be played. * {@code False} indicates that a haptic track is NOT present on the ringtone; * in this case the default vibration in {@link Ringer} should be trigger if needed. */ public @NonNull CompletableFuture<Boolean> play(RingtoneFactory factory, Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean isVibrationEnabled) { Log.d(this, "Posting play."); if (mHapticsFuture == null) { mHapticsFuture = new CompletableFuture<>(); } SomeArgs args = SomeArgs.obtain(); args.arg1 = factory; args.arg2 = incomingCall; args.arg3 = volumeShaperConfig; args.arg4 = isVibrationEnabled; postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); } /** Plays the ringtone. */ public void play(RingtoneFactory factory, Call incomingCall) { play(factory, incomingCall, null); return mHapticsFuture; } /** Stops playing the ringtone. */ Loading Loading @@ -146,9 +172,12 @@ public class AsyncRingtonePlayer { RingtoneFactory factory = (RingtoneFactory) args.arg1; Call incomingCall = (Call) args.arg2; VolumeShaper.Configuration volumeShaperConfig = (VolumeShaper.Configuration) args.arg3; boolean isVibrationEnabled = (boolean) args.arg4; args.recycle(); // don't bother with any of this if there is an EVENT_STOP waiting. if (mHandler.hasMessages(EVENT_STOP)) { mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } Loading @@ -156,11 +185,13 @@ public class AsyncRingtonePlayer { // anything. if(Uri.EMPTY.equals(incomingCall.getRingtone())) { mRingtone = null; mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } ThreadUtil.checkNotOnMainThread(); Log.i(this, "Play ringtone."); Log.i(this, "handlePlay: Play ringtone."); if (mRingtone == null) { mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig); Loading @@ -170,8 +201,29 @@ public class AsyncRingtonePlayer { ringtoneUri.toSafeString(); Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " + "factory. Skipping ringing. Uri was: " + ringtoneUriString); mHapticsFuture.complete(false /* ringtoneHasHaptics */); mHapticsFuture = null; return; } // With the ringtone to play now known, we can determine if it has haptic channels or // not; we will complete the haptics future so the default vibration code in Ringer // can know whether to trigger the vibrator. if (mHapticsFuture != null && !mHapticsFuture.isDone()) { boolean hasHaptics = factory.hasHapticChannels(mRingtone); Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics, isVibrationEnabled); if (!isVibrationEnabled && hasHaptics) { Log.i(this, "handlePlay: muting haptic channel"); mRingtone.setAudioAttributes( new AudioAttributes.Builder() .setMuteHapticChannels(true) .build()); } mHapticsFuture.complete(hasHaptics); mHapticsFuture = null; } } if (mShouldPauseBetweenRepeat) { Loading
src/com/android/server/telecom/Ringer.java +68 −11 Original line number Diff line number Diff line Loading @@ -32,11 +32,12 @@ import android.os.Bundle; import android.os.Vibrator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; /** Loading Loading @@ -138,6 +139,12 @@ public class Ringer { private final Vibrator mVibrator; private final InCallController mInCallController; private final VibrationEffectProxy mVibrationEffectProxy; private final boolean mIsHapticPlaybackSupportedByDevice; /** * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}. */ private CompletableFuture<Void> mBlockOnRingingFuture = null; private InCallTonePlayer mCallWaitingPlayer; private RingtoneFactory mRingtoneFactory; Loading Loading @@ -185,6 +192,14 @@ public class Ringer { mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN, PULSE_AMPLITUDE, REPEAT_VIBRATION_AT); } mIsHapticPlaybackSupportedByDevice = mSystemSettingsUtil.isHapticPlaybackSupported(mContext); } @VisibleForTesting public void setBlockOnRingingFuture(CompletableFuture<Void> future) { mBlockOnRingingFuture = future; } public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) { Loading Loading @@ -229,12 +244,18 @@ public class Ringer { "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, isSilentRingingRequested); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } return shouldAcquireAudioFocus; } stopCallWaiting(); VibrationEffect effect; CompletableFuture<Boolean> hapticsFuture = null; // Determine if the settings and DND mode indicate that the vibrator can be used right now. boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall); if (isRingerAudible) { mRingingCall = foregroundCall; Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER); Loading Loading @@ -277,9 +298,12 @@ public class Ringer { .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) .build(); } mRingtonePlayer.play(mRingtoneFactory, foregroundCall, mVolumeShaperConfig); hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, mVolumeShaperConfig, isVibratorEnabled); } else { mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null); // Ramping ringtone is not enabled. hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null, isVibratorEnabled); effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall); } } else { Loading @@ -291,23 +315,56 @@ public class Ringer { effect = mDefaultVibrationEffect; } if (shouldVibrate(mContext, foregroundCall) if (hapticsFuture != null) { CompletableFuture<Void> vibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> { if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) { Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b", isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice); maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, isRingerAudible); } else { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics"); } }); if (mBlockOnRingingFuture != null) { vibrateFuture.thenCompose( v -> { mBlockOnRingingFuture.complete(null); return null; }); } } else { if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } Log.w(this, "startRinging: No haptics future; fallback to default behavior"); maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, isRingerAudible); } return shouldAcquireAudioFocus; } private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact, VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) { if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) { if (mSystemSettingsUtil.applyRampingRinger(mContext) && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig() && isRingerAudible) { Log.i(this, "start vibration for ramping ringer."); mVibrator.vibrate(effect); mIsVibrating = true; } else { Log.i(this, "start normal vibration."); mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES); } mIsVibrating = true; } } else if (mIsVibrating) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating"); } return shouldAcquireAudioFocus; } private VibrationEffect createRampingRingerVibrationEffect(int vibrationSeconds) { Loading Loading @@ -443,7 +500,7 @@ public class Ringer { } } private boolean shouldVibrate(Context context, Call call) { private boolean isVibratorEnabled(Context context, Call call) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerModeInternal(); boolean shouldVibrate; Loading
src/com/android/server/telecom/RingtoneFactory.java +11 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,17 @@ public class RingtoneFactory { mCallsManager = callsManager; } /** * Determines if a ringtone has haptic channels. * @param ringtone The ringtone URI. * @return {@code true} if there is a haptic channel, {@code false} otherwise. */ public boolean hasHapticChannels(Ringtone ringtone) { boolean hasHapticChannels = RingtoneManager.hasHapticChannels(ringtone.getUri()); Log.i(this, "hasHapticChannels %s -> %b", ringtone.getUri(), hasHapticChannels); return hasHapticChannels; } public Ringtone getRingtone(Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig) { // Use the default ringtone of the work profile if the contact is a work profile contact. Loading
src/com/android/server/telecom/SystemSettingsUtil.java +5 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.content.Context; import android.media.AudioManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.telecom.Log; Loading Loading @@ -78,5 +79,9 @@ public class SystemSettingsUtil { return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_VIBRATION_DURATION, 0); } public boolean isHapticPlaybackSupported(Context context) { return context.getSystemService(AudioManager.class).isHapticPlaybackSupported(); } }
tests/src/com/android/server/telecom/tests/RingerTest.java +88 −22 File changed.Preview size limit exceeded, changes collapsed. Show changes