diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index f82cc22bd7648..0f2a9f21a0bcb 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -413,7 +413,7 @@ public abstract class VibrationEffect implements Parcelable { * * @hide */ - public abstract VibrationEffect resolve(int defaultAmplitude); + public abstract T resolve(int defaultAmplitude); /** * Scale the vibration effect intensity with the given constraints. @@ -425,7 +425,7 @@ public abstract class VibrationEffect implements Parcelable { * * @hide */ - public abstract VibrationEffect scale(float scaleFactor); + public abstract T scale(float scaleFactor); /** * Scale given vibration intensity by the given factor. diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 74fba0441be80..63fba25a37854 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -29,11 +29,8 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Resources; -import android.database.ContentObserver; import android.hardware.input.InputManager; import android.hardware.vibrator.IVibrator; -import android.hardware.vibrator.V1_0.EffectStrength; -import android.media.AudioManager; import android.os.BatteryStats; import android.os.Binder; import android.os.ExternalVibration; @@ -56,14 +53,10 @@ import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; -import android.os.UserHandle; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.WorkSource; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.util.DebugUtils; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; @@ -74,6 +67,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.vibrator.VibrationScaler; +import com.android.server.vibrator.VibrationSettings; import libcore.util.NativeAllocationRegistry; @@ -97,22 +92,6 @@ public class VibratorService extends IVibratorService.Stub private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 }; - // Scale levels. Each level, except MUTE, is defined as the delta between the current setting - // and the default intensity for that type of vibration (i.e. current - default). - private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100 - private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2 - private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1 - private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0 - private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1 - private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2 - - // Scale factors for each level. - private static final float SCALE_FACTOR_VERY_LOW = 0.6f; - private static final float SCALE_FACTOR_LOW = 0.8f; - private static final float SCALE_FACTOR_NONE = 1f; - private static final float SCALE_FACTOR_HIGH = 1.2f; - private static final float SCALE_FACTOR_VERY_HIGH = 1.4f; - // Default vibration attributes. Used when vibration is requested without attributes private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -120,11 +99,6 @@ public class VibratorService extends IVibratorService.Stub // Used to generate globally unique vibration ids. private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback - // A mapping from the intensity adjustment to the scaling to apply, where the intensity - // adjustment is defined as the delta between the default intensity level and the user selected - // intensity level. It's important that we apply the scaling on the delta between the two so - // that the default intensity level applies no scaling to application provided effects. - private final SparseArray mScaleLevels; private final LinkedList mPreviousRingVibrations; private final LinkedList mPreviousNotificationVibrations; private final LinkedList mPreviousAlarmVibrations; @@ -149,8 +123,8 @@ public class VibratorService extends IVibratorService.Stub private final String mSystemUiPackage; private PowerManagerInternal mPowerManagerInternal; private InputManager mIm; - private Vibrator mVibrator; - private SettingsObserver mSettingObserver; + private VibrationSettings mVibrationSettings; + private VibrationScaler mVibrationScaler; private final NativeWrapper mNativeWrapper; private volatile VibrateWaveformThread mThread; @@ -172,9 +146,6 @@ public class VibratorService extends IVibratorService.Stub @GuardedBy("mLock") private final RemoteCallbackList mVibratorStateListeners = new RemoteCallbackList<>(); - private int mHapticFeedbackIntensity; - private int mNotificationIntensity; - private int mRingIntensity; private SparseArray mAlwaysOnEffects = new SparseArray<>(); static native long vibratorInit(OnCompleteListener listener); @@ -343,7 +314,7 @@ public class VibratorService extends IVibratorService.Stub private ExternalVibrationHolder(ExternalVibration externalVibration) { this.externalVibration = externalVibration; - this.scale = SCALE_NONE; + this.scale = IExternalVibratorService.SCALE_NONE; mStartTimeDebug = System.currentTimeMillis(); mStatus = VibrationInfo.Status.RUNNING; } @@ -518,20 +489,6 @@ public class VibratorService extends IVibratorService.Stub } } - /** Represents the scale that must be applied to a vibration effect intensity. */ - private static final class ScaleLevel { - public final float factor; - - ScaleLevel(float factor) { - this.factor = factor; - } - - @Override - public String toString() { - return "ScaleLevel{factor=" + factor + "}"; - } - } - VibratorService(Context context) { this(context, new Injector()); } @@ -599,13 +556,6 @@ public class VibratorService extends IVibratorService.Stub mFallbackEffects.put(VibrationEffect.EFFECT_TEXTURE_TICK, VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)); - mScaleLevels = new SparseArray<>(); - mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW)); - mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW)); - mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_FACTOR_NONE)); - mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH)); - mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH)); - injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); } @@ -628,8 +578,8 @@ public class VibratorService extends IVibratorService.Stub Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady"); try { mIm = mContext.getSystemService(InputManager.class); - mVibrator = mContext.getSystemService(Vibrator.class); - mSettingObserver = new SettingsObserver(mH); + mVibrationSettings = new VibrationSettings(mContext, mH); + mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mPowerManagerInternal.registerLowPowerModeObserver( @@ -645,25 +595,7 @@ public class VibratorService extends IVibratorService.Stub } }); - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), - true, mSettingObserver, UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY), - true, mSettingObserver, UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY), - true, mSettingObserver, UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY), - true, mSettingObserver, UserHandle.USER_ALL); - - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), - true, mSettingObserver, UserHandle.USER_ALL); + mVibrationSettings.addListener(this::updateVibrators); mContext.registerReceiver(new BroadcastReceiver() { @Override @@ -686,17 +618,6 @@ public class VibratorService extends IVibratorService.Stub } } - private final class SettingsObserver extends ContentObserver { - public SettingsObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean SelfChange) { - updateVibrators(); - } - } - /** Callback for when vibration is complete, to be called by native. */ @VisibleForTesting public void onVibrationComplete(long vibrationId) { @@ -1118,11 +1039,10 @@ public class VibratorService extends IVibratorService.Stub private void startVibrationLocked(final Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage()); - if (!shouldVibrate(vib, intensity)) { + if (!shouldVibrate(vib)) { return; } - applyVibrationIntensityScalingLocked(vib, intensity); + applyVibrationIntensityScalingLocked(vib); startVibrationInnerLocked(vib); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); @@ -1171,73 +1091,12 @@ public class VibratorService extends IVibratorService.Stub || usage == VibrationAttributes.USAGE_COMMUNICATION_REQUEST; } - private int getCurrentIntensityLocked(int usageHint) { - if (isRingtone(usageHint)) { - return mRingIntensity; - } else if (isNotification(usageHint)) { - return mNotificationIntensity; - } else if (isHapticFeedback(usageHint)) { - return mHapticFeedbackIntensity; - } else if (isAlarm(usageHint)) { - return Vibrator.VIBRATION_INTENSITY_HIGH; - } else { - return Vibrator.VIBRATION_INTENSITY_MEDIUM; - } - } - - private int getDefaultIntensity(int usageHint) { - if (isRingtone(usageHint)) { - return mVibrator.getDefaultRingVibrationIntensity(); - } else if (isNotification(usageHint)) { - return mVibrator.getDefaultNotificationVibrationIntensity(); - } else if (isHapticFeedback(usageHint)) { - return mVibrator.getDefaultHapticFeedbackIntensity(); - } else if (isAlarm(usageHint)) { - return Vibrator.VIBRATION_INTENSITY_HIGH; - } else { - return Vibrator.VIBRATION_INTENSITY_MEDIUM; - } - } - - /** - * Scale the vibration effect by the intensity as appropriate based its intent. - */ - private void applyVibrationIntensityScalingLocked(Vibration vib, int intensity) { - if (vib.effect instanceof VibrationEffect.Prebaked) { - // Prebaked effects are always just a direct translation from intensity to - // EffectStrength. - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; - prebaked.setEffectStrength(intensityToEffectStrength(intensity)); - return; - } - - final int defaultIntensity = getDefaultIntensity(vib.attrs.getUsage()); - final ScaleLevel scale = mScaleLevels.get(intensity - defaultIntensity); - if (scale == null) { - // We should have scaling levels for all cases, so not being able to scale because of a - // missing level is unexpected. - Slog.e(TAG, "No configured scaling level!" - + " (current=" + intensity + ", default= " + defaultIntensity + ")"); - return; - } - - vib.originalEffect = vib.effect; - vib.effect = vib.effect.resolve(mDefaultVibrationAmplitude).scale(scale.factor); - vib.scale = scale.factor; - } - - private boolean shouldVibrateForRingtone() { - AudioManager audioManager = mContext.getSystemService(AudioManager.class); - int ringerMode = audioManager.getRingerModeInternal(); - // "Also vibrate for calls" Setting in Sound - if (Settings.System.getInt( - mContext.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0) { - return ringerMode != AudioManager.RINGER_MODE_SILENT; - } else if (Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) != 0) { - return ringerMode != AudioManager.RINGER_MODE_SILENT; - } else { - return ringerMode == AudioManager.RINGER_MODE_VIBRATE; + /** Scale the vibration effect by the intensity as appropriate based its intent. */ + private void applyVibrationIntensityScalingLocked(Vibration vib) { + VibrationEffect scaled = mVibrationScaler.scale(vib.effect, vib.attrs.getUsage()); + if (!scaled.equals(vib.effect)) { + vib.originalEffect = vib.effect; + vib.effect = scaled; } } @@ -1262,18 +1121,19 @@ public class VibratorService extends IVibratorService.Stub return mode; } - private boolean shouldVibrate(Vibration vib, int intensity) { + private boolean shouldVibrate(Vibration vib) { if (!shouldVibrateForPowerModeLocked(vib)) { endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_POWER); return false; } + int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage()); if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_SETTINGS); return false; } - if (vib.isRingtone() && !shouldVibrateForRingtone()) { + if (vib.isRingtone() && !mVibrationSettings.shouldVibrateForRingtone()) { if (DEBUG) { Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); } @@ -1335,7 +1195,6 @@ public class VibratorService extends IVibratorService.Stub synchronized (mLock) { boolean devicesUpdated = updateInputDeviceVibratorsLocked(); boolean lowPowerModeUpdated = updateLowPowerModeLocked(); - updateVibrationIntensityLocked(); if (devicesUpdated || lowPowerModeUpdated) { // If the state changes out from under us then just reset. @@ -1348,13 +1207,7 @@ public class VibratorService extends IVibratorService.Stub private boolean updateInputDeviceVibratorsLocked() { boolean changed = false; - boolean vibrateInputDevices = false; - try { - vibrateInputDevices = Settings.System.getIntForUser( - mContext.getContentResolver(), - Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0; - } catch (SettingNotFoundException snfe) { - } + boolean vibrateInputDevices = mVibrationSettings.shouldVibrateInputDevices(); if (vibrateInputDevices != mVibrateInputDevicesSetting) { changed = true; mVibrateInputDevicesSetting = vibrateInputDevices; @@ -1397,26 +1250,13 @@ public class VibratorService extends IVibratorService.Stub return false; } - private void updateVibrationIntensityLocked() { - mHapticFeedbackIntensity = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_INTENSITY, - mVibrator.getDefaultHapticFeedbackIntensity(), UserHandle.USER_CURRENT); - mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT); - mRingIntensity = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.RING_VIBRATION_INTENSITY, - mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT); - } - private void updateAlwaysOnLocked(int id, Vibration vib) { - final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage()); - if (!shouldVibrate(vib, intensity)) { + if (!shouldVibrate(vib)) { mNativeWrapper.vibratorAlwaysOnDisable(id); } else { - final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; - final int strength = intensityToEffectStrength(intensity); - mNativeWrapper.vibratorAlwaysOnEnable(id, prebaked.getId(), strength); + VibrationEffect.Prebaked scaled = mVibrationScaler.scale(vib.effect, + vib.attrs.getUsage()); + mNativeWrapper.vibratorAlwaysOnEnable(id, scaled.getId(), scaled.getEffectStrength()); } } @@ -1459,8 +1299,8 @@ public class VibratorService extends IVibratorService.Stub Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn"); try { synchronized (mInputDeviceVibrators) { - final VibrationEffect.OneShot oneShot = - (VibrationEffect.OneShot) vib.effect.resolve(mDefaultVibrationAmplitude); + final VibrationEffect.OneShot oneShot = vib.effect.resolve( + mDefaultVibrationAmplitude); if (DEBUG) { Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms" + " with amplitude " + oneShot.getAmplitude() + "."); @@ -1547,9 +1387,8 @@ public class VibratorService extends IVibratorService.Stub vib.opPkg, vib.reason + " (fallback)"); // Set current vibration before starting it, so callback will work. mCurrentVibration = fallbackVib; - final int intensity = getCurrentIntensityLocked(fallbackVib.attrs.getUsage()); linkVibration(fallbackVib); - applyVibrationIntensityScalingLocked(fallbackVib, intensity); + applyVibrationIntensityScalingLocked(fallbackVib); startVibrationInnerLocked(fallbackVib); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); @@ -1594,25 +1433,6 @@ public class VibratorService extends IVibratorService.Stub return mFallbackEffects.get(effectId); } - /** - * Return the current desired effect strength. - * - * If the returned value is < 0 then the vibration shouldn't be played at all. - */ - private static int intensityToEffectStrength(int intensity) { - switch (intensity) { - case Vibrator.VIBRATION_INTENSITY_LOW: - return EffectStrength.LIGHT; - case Vibrator.VIBRATION_INTENSITY_MEDIUM: - return EffectStrength.MEDIUM; - case Vibrator.VIBRATION_INTENSITY_HIGH: - return EffectStrength.STRONG; - default: - Slog.w(TAG, "Got unexpected vibration intensity: " + intensity); - return EffectStrength.STRONG; - } - } - private static boolean isNotification(int usageHint) { return usageHint == VibrationAttributes.USAGE_NOTIFICATION; } @@ -1690,14 +1510,7 @@ public class VibratorService extends IVibratorService.Stub pw.println(" mVibratorStateListeners Count=" + mVibratorStateListeners.getRegisteredCallbackCount()); pw.println(" mLowPowerMode=" + mLowPowerMode); - pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity); - pw.println(" mHapticFeedbackDefaultIntensity=" - + mVibrator.getDefaultHapticFeedbackIntensity()); - pw.println(" mNotificationIntensity=" + mNotificationIntensity); - pw.println(" mNotificationDefaultIntensity=" - + mVibrator.getDefaultNotificationVibrationIntensity()); - pw.println(" mRingIntensity=" + mRingIntensity); - pw.println(" mRingDefaultIntensity=" + mVibrator.getDefaultRingVibrationIntensity()); + pw.println(" mVibrationSettings=" + mVibrationSettings); pw.println(" mSupportedEffects=" + mSupportedEffects); pw.println(" mSupportedPrimitives=" + mSupportedPrimitives); pw.println(); @@ -1746,15 +1559,17 @@ public class VibratorService extends IVibratorService.Stub mVibratorUnderExternalControl); proto.write(VibratorServiceDumpProto.LOW_POWER_MODE, mLowPowerMode); proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, - mHapticFeedbackIntensity); + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)); proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, - mVibrator.getDefaultHapticFeedbackIntensity()); - proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity); + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)); proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, - mVibrator.getDefaultNotificationVibrationIntensity()); - proto.write(VibratorServiceDumpProto.RING_INTENSITY, mRingIntensity); + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + proto.write(VibratorServiceDumpProto.RING_INTENSITY, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY, - mVibrator.getDefaultRingVibrationIntensity()); + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); for (VibrationInfo info : mPreviousRingVibrations) { info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); @@ -2080,7 +1895,7 @@ public class VibratorService extends IVibratorService.Stub @Override public int onExternalVibrationStart(ExternalVibration vib) { if (!hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - return SCALE_MUTE; + return IExternalVibratorService.SCALE_MUTE; } if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, vib.getUid(), -1 /*owningUid*/, true /*exported*/) @@ -2088,7 +1903,7 @@ public class VibratorService extends IVibratorService.Stub Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + " tried to play externally controlled vibration" + " without VIBRATE permission, ignoring."); - return SCALE_MUTE; + return IExternalVibratorService.SCALE_MUTE; } int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); @@ -2101,10 +1916,9 @@ public class VibratorService extends IVibratorService.Stub } else { endVibrationLocked(vibHolder, VibrationInfo.Status.IGNORED_APP_OPS); } - return SCALE_MUTE; + return IExternalVibratorService.SCALE_MUTE; } - final int scaleLevel; synchronized (mLock) { if (mCurrentExternalVibration != null && mCurrentExternalVibration.externalVibration.equals(vib)) { @@ -2131,23 +1945,12 @@ public class VibratorService extends IVibratorService.Stub mCurrentExternalVibration = new ExternalVibrationHolder(vib); mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); vib.linkToDeath(mCurrentExternalDeathRecipient); + mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( + vib.getVibrationAttributes().getUsage()); if (DEBUG) { Slog.e(TAG, "Playing external vibration: " + vib); } - int usage = vib.getVibrationAttributes().getUsage(); - int defaultIntensity = getDefaultIntensity(usage); - int currentIntensity = getCurrentIntensityLocked(usage); - scaleLevel = currentIntensity - defaultIntensity; - } - if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { - mCurrentExternalVibration.scale = scaleLevel; - return scaleLevel; - } else { - // Presumably we want to play this but something about our scaling has gone - // wrong, so just play with no scaling. - Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level " - + scaleLevel + " for vibration " + vib); - return SCALE_NONE; + return mCurrentExternalVibration.scale; } } @@ -2232,21 +2035,12 @@ public class VibratorService extends IVibratorService.Stub } private boolean checkDoNotDisturb(CommonOptions opts) { - try { - final int zenMode = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ZEN_MODE); - if (zenMode != Settings.Global.ZEN_MODE_OFF && !opts.force) { - try (PrintWriter pw = getOutPrintWriter();) { - pw.print("Ignoring because device is on DND mode "); - pw.println(DebugUtils.flagsToString(Settings.Global.class, "ZEN_MODE_", - zenMode)); - return true; - } + if (mVibrationSettings.isInZenMode() && !opts.force) { + try (PrintWriter pw = getOutPrintWriter();) { + pw.print("Ignoring because device is on DND mode "); + return true; } - } catch (SettingNotFoundException e) { - // ignore } - return false; } diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java new file mode 100644 index 0000000000000..42a0a706a1c7f --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.content.Context; +import android.hardware.vibrator.V1_0.EffectStrength; +import android.os.IExternalVibratorService; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Slog; +import android.util.SparseArray; + +/** Controls vibration scaling. */ +// TODO(b/159207608): Make this package-private once vibrator services are moved to this package +public final class VibrationScaler { + private static final String TAG = "VibrationScaler"; + + // Scale levels. Each level, except MUTE, is defined as the delta between the current setting + // and the default intensity for that type of vibration (i.e. current - default). + private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100 + private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2 + private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1 + private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0 + private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1 + private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2 + + // Scale factors for each level. + private static final float SCALE_FACTOR_VERY_LOW = 0.6f; + private static final float SCALE_FACTOR_LOW = 0.8f; + private static final float SCALE_FACTOR_NONE = 1f; + private static final float SCALE_FACTOR_HIGH = 1.2f; + private static final float SCALE_FACTOR_VERY_HIGH = 1.4f; + + // A mapping from the intensity adjustment to the scaling to apply, where the intensity + // adjustment is defined as the delta between the default intensity level and the user selected + // intensity level. It's important that we apply the scaling on the delta between the two so + // that the default intensity level applies no scaling to application provided effects. + private final SparseArray mScaleLevels; + private final VibrationSettings mSettingsController; + private final int mDefaultVibrationAmplitude; + + public VibrationScaler(Context context, VibrationSettings settingsController) { + mSettingsController = settingsController; + mDefaultVibrationAmplitude = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultVibrationAmplitude); + + mScaleLevels = new SparseArray<>(); + mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW)); + mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW)); + mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_FACTOR_NONE)); + mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH)); + mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH)); + } + + /** + * Calculates the scale to be applied to external vibration with given usage. + * + * @param usageHint one of VibrationAttributes.USAGE_* + * @return one of IExternalVibratorService.SCALE_* + */ + public int getExternalVibrationScale(int usageHint) { + int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); + int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + int scaleLevel = currentIntensity - defaultIntensity; + + if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { + return scaleLevel; + } else { + // Something about our scaling has gone wrong, so just play with no scaling. + Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level " + + scaleLevel + " for vibration with usage " + usageHint); + return SCALE_NONE; + } + } + + /** + * Scale a {@link VibrationEffect} based on the given usage hint for this vibration. + * + * @param effect the effect to be scaled + * @param usageHint one of VibrationAttributes.USAGE_* + * @return The same given effect, if no changes were made, or a new {@link VibrationEffect} with + * resolved and scaled amplitude + */ + public T scale(VibrationEffect effect, int usageHint) { + if (effect instanceof VibrationEffect.Prebaked) { + // Prebaked effects are always just a direct translation to EffectStrength. + int intensity = mSettingsController.getCurrentIntensity(usageHint); + int newStrength = intensityToEffectStrength(intensity); + VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; + + if (prebaked.getEffectStrength() == newStrength) { + return (T) prebaked; + } + + return (T) new VibrationEffect.Prebaked( + prebaked.getId(), prebaked.shouldFallback(), newStrength); + } + + effect = effect.resolve(mDefaultVibrationAmplitude); + int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); + int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity); + + if (scale == null) { + // Something about our scaling has gone wrong, so just play with no scaling. + Slog.e(TAG, "No configured scaling level!" + + " (current=" + currentIntensity + ", default= " + defaultIntensity + ")"); + return (T) effect; + } + + return effect.scale(scale.factor); + } + + + + /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */ + private static int intensityToEffectStrength(int intensity) { + switch (intensity) { + case Vibrator.VIBRATION_INTENSITY_LOW: + return EffectStrength.LIGHT; + case Vibrator.VIBRATION_INTENSITY_MEDIUM: + return EffectStrength.MEDIUM; + case Vibrator.VIBRATION_INTENSITY_HIGH: + return EffectStrength.STRONG; + default: + Slog.w(TAG, "Got unexpected vibration intensity: " + intensity); + return EffectStrength.STRONG; + } + } + + /** Represents the scale that must be applied to a vibration effect intensity. */ + private static final class ScaleLevel { + public final float factor; + + ScaleLevel(float factor) { + this.factor = factor; + } + + @Override + public String toString() { + return "ScaleLevel{factor=" + factor + "}"; + } + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java new file mode 100644 index 0000000000000..ad2b3354b5303 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.content.Context; +import android.database.ContentObserver; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.Vibrator; +import android.provider.Settings; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** Controls all the system settings related to vibration. */ +// TODO(b/159207608): Make this package-private once vibrator services are moved to this package +public final class VibrationSettings { + private static final String TAG = "VibrationSettings"; + + /** Listener for changes on vibration settings. */ + public interface OnVibratorSettingsChanged { + /** Callback triggered when any of the vibrator settings change. */ + void onChange(); + } + + private final Object mLock = new Object(); + private final Context mContext; + private final Vibrator mVibrator; + private final AudioManager mAudioManager; + private final SettingsObserver mSettingObserver; + + @GuardedBy("mLock") + private final List mListeners = new ArrayList<>(); + + @GuardedBy("mLock") + private boolean mVibrateInputDevices; + @GuardedBy("mLock") + private boolean mVibrateWhenRinging; + @GuardedBy("mLock") + private boolean mApplyRampingRinger; + @GuardedBy("mLock") + private int mZenMode; + @GuardedBy("mLock") + private int mHapticFeedbackIntensity; + @GuardedBy("mLock") + private int mNotificationIntensity; + @GuardedBy("mLock") + private int mRingIntensity; + + public VibrationSettings(Context context, Handler handler) { + mContext = context; + mVibrator = context.getSystemService(Vibrator.class); + mAudioManager = context.getSystemService(AudioManager.class); + mSettingObserver = new SettingsObserver(handler); + + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); + registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER)); + registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); + + // Update with current values from settings. + updateSettings(); + } + + /** + * Add listener to vibrator settings changes. This will trigger the listener with current state + * immediately and every time one of the settings change. + */ + public void addListener(OnVibratorSettingsChanged listener) { + synchronized (mLock) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + } + + /** Remove listener to vibrator settings. */ + public void removeListener(OnVibratorSettingsChanged listener) { + synchronized (mLock) { + mListeners.remove(listener); + } + } + + /** + * Return default vibration intensity for given usage. + * + * @param usageHint one of VibrationAttributes.USAGE_* + * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_* + */ + public int getDefaultIntensity(int usageHint) { + if (isRingtone(usageHint)) { + return mVibrator.getDefaultRingVibrationIntensity(); + } else if (isNotification(usageHint)) { + return mVibrator.getDefaultNotificationVibrationIntensity(); + } else if (isHapticFeedback(usageHint)) { + return mVibrator.getDefaultHapticFeedbackIntensity(); + } else if (isAlarm(usageHint)) { + return Vibrator.VIBRATION_INTENSITY_HIGH; + } else { + return Vibrator.VIBRATION_INTENSITY_MEDIUM; + } + } + + /** + * Return the current vibration intensity set for given usage at the user settings. + * + * @param usageHint one of VibrationAttributes.USAGE_* + * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_* + */ + public int getCurrentIntensity(int usageHint) { + synchronized (mLock) { + if (isRingtone(usageHint)) { + return mRingIntensity; + } else if (isNotification(usageHint)) { + return mNotificationIntensity; + } else if (isHapticFeedback(usageHint)) { + return mHapticFeedbackIntensity; + } else if (isAlarm(usageHint)) { + return Vibrator.VIBRATION_INTENSITY_HIGH; + } else { + return Vibrator.VIBRATION_INTENSITY_MEDIUM; + } + } + } + + /** + * Return {@code true} if the device should vibrate for ringtones. + * + *

This checks the current {@link AudioManager#getRingerMode()} against user settings for + * vibrations while ringing. + */ + public boolean shouldVibrateForRingtone() { + int ringerMode = mAudioManager.getRingerModeInternal(); + synchronized (mLock) { + if (mVibrateWhenRinging) { + return ringerMode != AudioManager.RINGER_MODE_SILENT; + } else if (mApplyRampingRinger) { + return ringerMode != AudioManager.RINGER_MODE_SILENT; + } else { + return ringerMode == AudioManager.RINGER_MODE_VIBRATE; + } + } + } + + /** Return {@code true} if input devices should vibrate instead of this device. */ + public boolean shouldVibrateInputDevices() { + return mVibrateInputDevices; + } + + /** Return {@code true} if setting for {@link Settings.Global#ZEN_MODE} is not OFF. */ + public boolean isInZenMode() { + return mZenMode != Settings.Global.ZEN_MODE_OFF; + } + + private static boolean isNotification(int usageHint) { + return usageHint == VibrationAttributes.USAGE_NOTIFICATION; + } + + private static boolean isRingtone(int usageHint) { + return usageHint == VibrationAttributes.USAGE_RINGTONE; + } + + private static boolean isHapticFeedback(int usageHint) { + return usageHint == VibrationAttributes.USAGE_TOUCH; + } + + private static boolean isAlarm(int usageHint) { + return usageHint == VibrationAttributes.USAGE_ALARM; + } + + /** Updates all vibration settings and triggers registered listeners. */ + @VisibleForTesting + public void updateSettings() { + List currentListeners; + synchronized (mLock) { + mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; + mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0; + mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + mVibrator.getDefaultHapticFeedbackIntensity()); + mNotificationIntensity = getSystemSetting( + Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + mVibrator.getDefaultNotificationVibrationIntensity()); + mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, + mVibrator.getDefaultRingVibrationIntensity()); + mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; + mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + currentListeners = new ArrayList<>(mListeners); + } + + for (OnVibratorSettingsChanged listener : currentListeners) { + listener.onChange(); + } + } + + @Override + public String toString() { + return "VibrationSettings{" + + "mVibrateInputDevices=" + mVibrateInputDevices + + ", mVibrateWhenRinging=" + mVibrateWhenRinging + + ", mApplyRampingRinger=" + mApplyRampingRinger + + ", mZenMode=" + Settings.Global.zenModeToString(mZenMode) + + ", mHapticFeedbackIntensity=" + + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)) + + ", mHapticFeedbackDefaultIntensity=" + + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)) + + ", mNotificationIntensity=" + + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + ", mNotificationDefaultIntensity=" + + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + ", mRingIntensity=" + + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)) + + ", mRingDefaultIntensity=" + + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)) + + '}'; + } + + private static String intensityToString(int intensity) { + switch (intensity) { + case Vibrator.VIBRATION_INTENSITY_OFF: + return "OFF"; + case Vibrator.VIBRATION_INTENSITY_LOW: + return "LOW"; + case Vibrator.VIBRATION_INTENSITY_MEDIUM: + return "MEDIUM"; + case Vibrator.VIBRATION_INTENSITY_HIGH: + return "HIGH"; + default: + return "UNKNOWN INTENSITY " + intensity; + } + } + + private int getSystemSetting(String settingName, int defaultValue) { + return Settings.System.getIntForUser(mContext.getContentResolver(), + settingName, defaultValue, UserHandle.USER_CURRENT); + } + + private int getGlobalSetting(String settingName, int defaultValue) { + return Settings.Global.getInt(mContext.getContentResolver(), settingName, defaultValue); + } + + private void registerSettingsObserver(Uri settingUri) { + mContext.getContentResolver().registerContentObserver( + settingUri, /* notifyForDescendants= */ true, mSettingObserver, + UserHandle.USER_ALL); + } + + /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + updateSettings(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java index fba321e072072..5ddb5712884bc 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java @@ -39,11 +39,13 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManagerInternal; import android.hardware.vibrator.IVibrator; import android.media.AudioAttributes; +import android.media.AudioManager; import android.os.Handler; import android.os.IBinder; import android.os.IVibratorStateListener; @@ -61,11 +63,11 @@ import android.os.Vibrator; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.Settings; -import android.test.mock.MockContentResolver; import androidx.test.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; import org.junit.After; import org.junit.Before; @@ -107,11 +109,13 @@ public class VibratorServiceTest { new VibrationAttributes.Builder().setUsage( VibrationAttributes.USAGE_RINGTONE).build(); - @Rule public MockitoRule rule = MockitoJUnit.rule(); + @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @Mock private PackageManagerInternal mPackageManagerInternalMock; @Mock private PowerManagerInternal mPowerManagerInternalMock; @Mock private PowerSaveState mPowerSaveStateMock; + // TODO(b/131311651): replace with a FakeVibrator instead. @Mock private Vibrator mVibratorMock; @Mock private AppOpsManager mAppOpsManagerMock; @Mock private VibratorService.NativeWrapper mNativeWrapperMock; @@ -126,9 +130,7 @@ public class VibratorServiceTest { mTestLooper = new TestLooper(); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); - MockContentResolver contentResolver = new MockContentResolver(mContextSpy); - contentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - + ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(contentResolver); when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibratorMock); when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock); @@ -144,28 +146,26 @@ public class VibratorServiceTest { when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION)) .thenReturn(mPowerSaveStateMock); - setVibrationIntensityUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setVibrationIntensityUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); - setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); - setVibrationIntensityUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock); addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); - FakeSettingsProvider.clearSettingsProvider(); } @After public void tearDown() throws Exception { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(PowerManagerInternal.class); - FakeSettingsProvider.clearSettingsProvider(); } private VibratorService createService() { - return new VibratorService(mContextSpy, + VibratorService service = new VibratorService(mContextSpy, new VibratorService.Injector() { @Override VibratorService.NativeWrapper getNativeWrapper() { @@ -182,6 +182,8 @@ public class VibratorServiceTest { // ignore } }); + service.systemReady(); + return service; } @Test @@ -304,10 +306,30 @@ public class VibratorServiceTest { verify(mNativeWrapperMock, never()).vibratorAlwaysOnEnable(anyLong(), anyLong(), anyLong()); } + @Test + public void vibrate_withRingtone_usesRingtoneSettings() { + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + vibrate(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + vibrate(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS); + + InOrder inOrderVerifier = inOrder(mNativeWrapperMock); + inOrderVerifier.verify(mNativeWrapperMock, never()).vibratorOn(eq(1L), anyLong()); + inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(10L), anyLong()); + inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L), anyLong()); + } + @Test public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() { VibratorService service = createService(); - service.systemReady(); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); AudioAttributes audioAttributes = new AudioAttributes.Builder() @@ -324,7 +346,6 @@ public class VibratorServiceTest { @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { VibratorService service = createService(); - service.systemReady(); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS); @@ -624,14 +645,13 @@ public class VibratorServiceTest { @Test public void scale_withPrebaked_userIntensitySettingAsEffectStrength() { // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - setVibrationIntensityUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); - setVibrationIntensityUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); - setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF); VibratorService service = createService(); - service.systemReady(); vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); @@ -659,16 +679,15 @@ public class VibratorServiceTest { public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception { when(mVibratorMock.getDefaultNotificationVibrationIntensity()) .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); - setVibrationIntensityUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - setVibrationIntensityUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); - setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF); mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorService service = createService(); - service.systemReady(); vibrate(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS); vibrate(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS); @@ -694,16 +713,15 @@ public class VibratorServiceTest { public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() { when(mVibratorMock.getDefaultNotificationVibrationIntensity()) .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); - setVibrationIntensityUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - setVibrationIntensityUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); - setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF); mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); VibratorService service = createService(); - service.systemReady(); VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) @@ -785,8 +803,18 @@ public class VibratorServiceTest { LocalServices.addService(clazz, mock); } - private void setVibrationIntensityUserSetting(String settingName, int value) { + private void setRingerMode(int ringerMode) { + AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class); + audioManager.setRingerModeInternal(ringerMode); + assertEquals(ringerMode, audioManager.getRingerMode()); + } + + private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); } + + private void setGlobalSetting(String settingName, int value) { + Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value); + } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java new file mode 100644 index 0000000000000..fa171fdeaece3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Handler; +import android.os.IExternalVibratorService; +import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** + * Tests for {@link VibrationScaler}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:VibrationScalerTest + */ +@Presubmit +public class VibrationScalerTest { + + @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + + // TODO(b/131311651): replace with a FakeVibrator instead. + @Mock private Vibrator mVibratorMock; + + private TestLooper mTestLooper; + private ContextWrapper mContextSpy; + private VibrationSettings mVibrationSettings; + private VibrationScaler mVibrationScaler; + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + + ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); + when(mContextSpy.getContentResolver()).thenReturn(contentResolver); + when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibratorMock); + + mVibrationSettings = new VibrationSettings( + mContextSpy, new Handler(mTestLooper.getLooper())); + mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings); + } + + @Test + public void testGetExternalVibrationScale() { + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + assertEquals(IExternalVibratorService.SCALE_VERY_HIGH, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + assertEquals(IExternalVibratorService.SCALE_HIGH, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + assertEquals(IExternalVibratorService.SCALE_NONE, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM); + assertEquals(IExternalVibratorService.SCALE_LOW, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_HIGH); + assertEquals(IExternalVibratorService.SCALE_VERY_LOW, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + // Unexpected vibration intensity will be treated as SCALE_NONE. + assertEquals(IExternalVibratorService.SCALE_NONE, + mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH)); + } + + @Test + public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() { + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); + + VibrationEffect.Prebaked scaled = mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION); + // Unexpected intensity setting will be mapped to STRONG. + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + } + + @Test + public void scale_withOneShotAndWaveform_resolvesAmplitude() { + // No scale, default amplitude still resolved + when(mVibratorMock.getDefaultRingVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + + VibrationEffect.OneShot oneShot = mVibrationScaler.scale( + VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE), + VibrationAttributes.USAGE_RINGTONE); + assertTrue(oneShot.getAmplitude() > 0); + + VibrationEffect.Waveform waveform = mVibrationScaler.scale( + VibrationEffect.createWaveform(new long[]{10}, + new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1), + VibrationAttributes.USAGE_RINGTONE); + assertTrue(waveform.getAmplitudes()[0] > 0); + } + + @Test + public void scale_withOneShotWaveform_scalesAmplitude() { + when(mVibratorMock.getDefaultRingVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + when(mVibratorMock.getDefaultNotificationVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + + VibrationEffect.OneShot oneShot = mVibrationScaler.scale( + VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE); + // Ringtone scales up. + assertTrue(oneShot.getAmplitude() > 100); + + VibrationEffect.Waveform waveform = mVibrationScaler.scale( + VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1), + VibrationAttributes.USAGE_NOTIFICATION); + // Notification scales down. + assertTrue(waveform.getAmplitudes()[0] < 100); + + oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100), + VibrationAttributes.USAGE_TOUCH); + // Haptic feedback does not scale. + assertEquals(100, oneShot.getAmplitude()); + } + + @Test + public void scale_withComposed_scalesPrimitives() { + when(mVibratorMock.getDefaultRingVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + when(mVibratorMock.getDefaultNotificationVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + + VibrationEffect composed = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose(); + + VibrationEffect.Composed scaled = mVibrationScaler.scale(composed, + VibrationAttributes.USAGE_RINGTONE); + // Ringtone scales up. + assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f); + + scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION); + // Notification scales down. + assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f); + + scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH); + // Haptic feedback does not scale. + assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5); + } + + private void setUserSetting(String settingName, int value) { + Settings.System.putIntForUser( + mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); + // FakeSettingsProvider don't support testing triggering ContentObserver yet. + mVibrationSettings.updateSettings(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java new file mode 100644 index 0000000000000..8aa0e3fec60f7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.media.AudioManager; +import android.os.Handler; +import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.Vibrator; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** + * Tests for {@link VibrationSettings}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:VibrationSettingsTest + */ +@Presubmit +public class VibrationSettingsTest { + + @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + + // TODO(b/131311651): replace with a FakeVibrator instead. + @Mock private Vibrator mVibratorMock; + @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock; + + private TestLooper mTestLooper; + private ContextWrapper mContextSpy; + private AudioManager mAudioManager; + private VibrationSettings mVibrationSettings; + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + + ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); + when(mContextSpy.getContentResolver()).thenReturn(contentResolver); + when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibratorMock); + when(mVibratorMock.hasVibrator()).thenReturn(true); + + mAudioManager = mContextSpy.getSystemService(AudioManager.class); + mVibrationSettings = new VibrationSettings( + mContextSpy, new Handler(mTestLooper.getLooper())); + + setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + } + + @After + public void tearDown() throws Exception { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Test + public void addListener_settingsChangeTriggerListener() { + mVibrationSettings.addListener(mListenerMock); + + setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + + verify(mListenerMock, times(7)).onChange(); + } + + @Test + public void removeListener_noMoreCallbacksToListener() { + mVibrationSettings.addListener(mListenerMock); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + verify(mListenerMock).onChange(); + + mVibrationSettings.removeListener(mListenerMock); + + verifyNoMoreInteractions(mListenerMock); + setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS); + } + + @Test + public void shouldVibrateForRingtones_withVibrateWhenRinging_onlyIgnoreSettingsForSilentMode() { + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT); + assertEquals(AudioManager.RINGER_MODE_SILENT, mAudioManager.getRingerMode()); + assertFalse(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_MAX); + assertEquals(AudioManager.RINGER_MODE_MAX, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); + assertEquals(AudioManager.RINGER_MODE_NORMAL, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); + assertEquals(AudioManager.RINGER_MODE_VIBRATE, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + } + + @Test + public void shouldVibrateForRingtones_withApplyRampingRinger_onlyIgnoreSettingsForSilentMode() { + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT); + assertEquals(AudioManager.RINGER_MODE_SILENT, mAudioManager.getRingerMode()); + assertFalse(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_MAX); + assertEquals(AudioManager.RINGER_MODE_MAX, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); + assertEquals(AudioManager.RINGER_MODE_NORMAL, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); + assertEquals(AudioManager.RINGER_MODE_VIBRATE, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + } + + @Test + public void shouldVibrateForRingtones_withAllSettingsOff_onlyVibratesForVibrateMode() { + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); + assertEquals(AudioManager.RINGER_MODE_VIBRATE, mAudioManager.getRingerMode()); + assertTrue(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT); + assertEquals(AudioManager.RINGER_MODE_SILENT, mAudioManager.getRingerMode()); + assertFalse(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_MAX); + assertEquals(AudioManager.RINGER_MODE_MAX, mAudioManager.getRingerMode()); + assertFalse(mVibrationSettings.shouldVibrateForRingtone()); + + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); + assertEquals(AudioManager.RINGER_MODE_NORMAL, mAudioManager.getRingerMode()); + assertFalse(mVibrationSettings.shouldVibrateForRingtone()); + } + + @Test + public void shouldVibrateInputDevices_returnsSettingsValue() { + setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); + assertTrue(mVibrationSettings.shouldVibrateInputDevices()); + + setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0); + assertFalse(mVibrationSettings.shouldVibrateInputDevices()); + } + + @Test + public void isInZenMode_returnsSettingsValue() { + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + assertFalse(mVibrationSettings.isInZenMode()); + + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS); + assertTrue(mVibrationSettings.isInZenMode()); + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS); + assertTrue(mVibrationSettings.isInZenMode()); + + setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + assertFalse(mVibrationSettings.isInZenMode()); + + setGlobalSetting(Settings.Global.ZEN_MODE, + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + assertTrue(mVibrationSettings.isInZenMode()); + } + + @Test + public void getDefaultIntensity_returnsIntensityFromVibratorService() { + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_HIGH); + when(mVibratorMock.getDefaultNotificationVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM); + when(mVibratorMock.getDefaultRingVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_LOW); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + + assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); + assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity( + VibrationAttributes.USAGE_PHYSICAL_EMULATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, + mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + } + + @Test + public void getCurrentIntensity_returnsIntensityFromSettings() { + when(mVibratorMock.getDefaultHapticFeedbackIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_OFF); + when(mVibratorMock.getDefaultNotificationVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_OFF); + when(mVibratorMock.getDefaultRingVibrationIntensity()) + .thenReturn(Vibrator.VIBRATION_INTENSITY_OFF); + + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + + assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM)); + assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN)); + assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity( + VibrationAttributes.USAGE_PHYSICAL_EMULATION)); + assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + } + + private void setUserSetting(String settingName, int value) { + Settings.System.putIntForUser( + mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); + // FakeSettingsProvider don't support testing triggering ContentObserver yet. + mVibrationSettings.updateSettings(); + } + + private void setGlobalSetting(String settingName, int value) { + Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value); + // FakeSettingsProvider don't support testing triggering ContentObserver yet. + mVibrationSettings.updateSettings(); + mAudioManager.reloadAudioSettings(); + } +} diff --git a/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java index e482708e385b4..a0a5a293f2e42 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java @@ -91,6 +91,14 @@ public class FakeSettingsProvider extends MockContentProvider { } } + /** + * Creates a {@link org.junit.rules.TestRule} that makes sure {@link #clearSettingsProvider()} + * is triggered before and after each test. + */ + public static FakeSettingsProviderRule rule() { + return new FakeSettingsProviderRule(); + } + /** * This needs to be called before and after using the FakeSettingsProvider class. */ diff --git a/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProviderRule.java b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProviderRule.java new file mode 100644 index 0000000000000..c4e9545dfde29 --- /dev/null +++ b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProviderRule.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.test; + +import android.content.Context; +import android.provider.Settings; +import android.test.mock.MockContentResolver; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * JUnit Rule helps keeping test {@link FakeSettingsProvider} clean. + * + *

It clears {@link FakeSettingsProvider} before and after each test. Example use: + *


+ * public class ExampleTest {
+ *
+ *     @Rule public FakeSettingsProviderRule rule = FakeSettingsProvider.rule();
+ *
+ *     @Test
+ *     public void shouldDoSomething() {
+ *         ContextResolver cr = rule.mockContentResolver(mContext);
+ *         Settings.Global.putInt(cr, "my_setting_name", 1);
+ *         // Test code relying on my_setting_name value using cr
+ *     }
+ * }
+ * 
+ * + * @see FakeSettingsProvider + */ +public final class FakeSettingsProviderRule implements TestRule { + + /** Prevent initialization outside {@link FakeSettingsProvider}. */ + FakeSettingsProviderRule() { + } + + /** + * Creates a {@link MockContentResolver} that uses the {@link FakeSettingsProvider} as the + * {@link Settings#AUTHORITY} provider. + */ + public MockContentResolver mockContentResolver(Context context) { + MockContentResolver contentResolver = new MockContentResolver(context); + contentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + return contentResolver; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + public void evaluate() throws Throwable { + FakeSettingsProvider.clearSettingsProvider(); + try { + base.evaluate(); + } finally { + FakeSettingsProvider.clearSettingsProvider(); + } + } + }; + } +}