diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 642c618ba1f7c..5bed32cb0438f 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -166,12 +166,28 @@ public final class VibrationAttributes implements Parcelable { */ public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 0x2; + /** + * Flag requesting vibration effect to be played with fresh user settings values. + * + *

This flag is not protected by any permission, but vibrations that use it require an extra + * query of user vibration intensity settings, ringer mode and other controls that affect the + * vibration effect playback, which can increase the latency for the overall request. + * + *

This is intended to be used on scenarios where the user settings might have changed + * recently, and needs to be applied to this vibration, like settings controllers that preview + * newly set intensities to the user. + * + * @hide + */ + public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x3; + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ public static final int FLAG_ALL_SUPPORTED = - FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF + | FLAG_INVALIDATE_SETTINGS_CACHE; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) { @@ -446,8 +462,10 @@ public final class VibrationAttributes implements Parcelable { } /** - * Set flags - * @param flags combination of flags to be set. + * Sets only the flags specified in the bitmask, leaving the other supported flag values + * unchanged in the builder. + * + * @param flags Combination of flags to be set. * @param mask Bit range that should be changed. * @return the same Builder instance. */ @@ -456,5 +474,18 @@ public final class VibrationAttributes implements Parcelable { mFlags = (mFlags & ~mask) | (flags & mask); return this; } + + /** + * Set all supported flags with given combination of flags, overriding any previous values + * set to this builder. + * + * @param flags combination of flags to be set. + * @return the same Builder instance. + * + * @hide + */ + public @NonNull Builder setFlags(@Flag int flags) { + return setFlags(flags, FLAG_ALL_SUPPORTED); + } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1ac373f074ecf..5aa81ac9086d8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5446,8 +5446,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { VibrationAttributes attrs = getVibrationAttributes(effectId); if (always) { attrs = new VibrationAttributes.Builder(attrs) - .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, - VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build(); } mVibrator.vibrate(uid, packageName, effect, reason, attrs); diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 77da751189581..e9535e0a56e1f 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -121,6 +121,11 @@ final class VibrationSettings { USAGE_PHYSICAL_EMULATION, USAGE_HARDWARE_FEEDBACK)); + private static final IntentFilter USER_SWITCHED_INTENT_FILTER = + new IntentFilter(Intent.ACTION_USER_SWITCHED); + private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER = + new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + /** Listener for changes on vibration settings. */ interface OnVibratorSettingsChanged { /** Callback triggered when any of the vibrator settings change. */ @@ -130,11 +135,11 @@ final class VibrationSettings { private final Object mLock = new Object(); private final Context mContext; private final String mSystemUiPackage; - private final SettingsObserver mSettingObserver; + private final SettingsContentObserver mSettingObserver; @VisibleForTesting final UidObserver mUidObserver; @VisibleForTesting - final UserObserver mUserReceiver; + final SettingsBroadcastReceiver mSettingChangeReceiver; @GuardedBy("mLock") private final List mListeners = new ArrayList<>(); @@ -154,6 +159,8 @@ final class VibrationSettings { private boolean mBatterySaverMode; @GuardedBy("mLock") private boolean mVibrateOn; + @GuardedBy("mLock") + private int mRingerMode; VibrationSettings(Context context, Handler handler) { this(context, handler, new VibrationConfig(context.getResources())); @@ -163,9 +170,9 @@ final class VibrationSettings { VibrationSettings(Context context, Handler handler, VibrationConfig config) { mContext = context; mVibrationConfig = config; - mSettingObserver = new SettingsObserver(handler); + mSettingObserver = new SettingsContentObserver(handler); mUidObserver = new UidObserver(); - mUserReceiver = new UserObserver(); + mSettingChangeReceiver = new SettingsBroadcastReceiver(); mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) .getSystemUiServiceComponent().getPackageName(); @@ -188,12 +195,13 @@ final class VibrationSettings { VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)); // Update with current values from settings. - updateSettings(); + update(); } public void onSystemReady() { synchronized (mLock) { mAudioManager = mContext.getSystemService(AudioManager.class); + mRingerMode = mAudioManager.getRingerModeInternal(); } try { ActivityManager.getService().registerUidObserver(mUidObserver, @@ -224,8 +232,8 @@ final class VibrationSettings { } }); - IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(mUserReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER); + registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity. registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); @@ -248,7 +256,7 @@ final class VibrationSettings { Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); // Update with newly loaded services. - updateSettings(); + update(); } /** @@ -396,16 +404,17 @@ final class VibrationSettings { // Only ringtone and notification vibrations are disabled when phone is on silent mode. return true; } - // If audio manager was not loaded yet then assume most restrictive mode. - int ringerMode = (mAudioManager == null) - ? AudioManager.RINGER_MODE_SILENT - : mAudioManager.getRingerModeInternal(); - return ringerMode != AudioManager.RINGER_MODE_SILENT; + return mRingerMode != AudioManager.RINGER_MODE_SILENT; } - /** Updates all vibration settings and triggers registered listeners. */ - @VisibleForTesting - void updateSettings() { + /** Update all cached settings and triggers registered listeners. */ + void update() { + updateSettings(); + updateRingerMode(); + notifyListeners(); + } + + private void updateSettings() { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; @@ -435,7 +444,6 @@ final class VibrationSettings { loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1), getDefaultIntensity(USAGE_RINGTONE)); - mCurrentVibrationIntensities.clear(); mCurrentVibrationIntensities.put(USAGE_ALARM, alarmIntensity); mCurrentVibrationIntensities.put(USAGE_NOTIFICATION, notificationIntensity); @@ -469,7 +477,16 @@ final class VibrationSettings { // A11y is not disabled by any haptic feedback setting. mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity); } - notifyListeners(); + } + + private void updateRingerMode() { + synchronized (mLock) { + // If audio manager was not loaded yet then assume most restrictive mode. + // This will be loaded again as soon as the audio manager is loaded in onSystemReady. + mRingerMode = (mAudioManager == null) + ? AudioManager.RINGER_MODE_SILENT + : mAudioManager.getRingerModeInternal(); + } } @Override @@ -586,6 +603,11 @@ final class VibrationSettings { UserHandle.USER_ALL); } + private void registerSettingsChangeReceiver(IntentFilter intentFilter) { + mContext.registerReceiver(mSettingChangeReceiver, intentFilter, + Context.RECEIVER_NOT_EXPORTED); + } + @Nullable private VibrationEffect createEffectFromResource(int resId) { long[] timings = getLongIntArray(mContext.getResources(), resId); @@ -616,24 +638,33 @@ final class VibrationSettings { } /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ - private final class SettingsObserver extends ContentObserver { - SettingsObserver(Handler handler) { + private final class SettingsContentObserver extends ContentObserver { + SettingsContentObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { updateSettings(); + notifyListeners(); } } - /** Implementation of {@link BroadcastReceiver} to update settings on current user change. */ + /** + * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer + * mode change. + */ @VisibleForTesting - final class UserObserver extends BroadcastReceiver { + final class SettingsBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { - updateSettings(); + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + // Reload all settings, as they are user-based. + update(); + } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { + updateRingerMode(); + notifyListeners(); } } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 1260e5dbc9f71..1f08b35d5540c 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -401,6 +401,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { uid, opPkg, reason); fillVibrationFallbacks(vib, effect); + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } + synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "Starting vibrate for vibration " + vib.id); @@ -1506,12 +1512,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return IExternalVibratorService.SCALE_MUTE; } + VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(), + /* effect= */ null); + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } + boolean alreadyUnderExternalControl = false; boolean waitForCompletion = false; int scale; synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( - vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); + vib.getUid(), vib.getPackage(), attrs); if (ignoreStatus != null) { ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); vibHolder.scale = IExternalVibratorService.SCALE_MUTE; @@ -1549,7 +1563,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mCurrentExternalVibration = new ExternalVibrationHolder(vib); vib.linkToDeath(mCurrentExternalVibration); mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( - vib.getVibrationAttributes().getUsage()); + attrs.getUsage()); scale = mCurrentExternalVibration.scale; } @@ -1908,7 +1922,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final int flags = commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0; return new VibrationAttributes.Builder() - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .setFlags(flags) // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. .setUsage(VibrationAttributes.USAGE_TOUCH) .build(); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index 0301e94a6accf..b907c62be6fbe 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -277,6 +277,6 @@ public class VibrationScalerTest { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider don't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index ec16188bfc1d6..0c28d8c761aba 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -475,6 +475,24 @@ public class VibrationSettingsTest { } } + @Test + public void shouldIgnoreVibration_updateTriggeredAfterInternalRingerModeChanged() { + // Vibrating settings on are overruled by ringer mode. + setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + + assertVibrationNotIgnoredForUsage(USAGE_RINGTONE); + + // Testing the broadcast flow manually. + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT); + mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + + assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE); + } + @Test public void shouldVibrateInputDevices_returnsSettingsValue() { setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); @@ -577,7 +595,7 @@ public class VibrationSettingsTest { Settings.System.putIntForUser(mContextSpy.getContentResolver(), Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, UserHandle.USER_CURRENT); - mVibrationSettings.mUserReceiver.onReceive(mContextSpy, + mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED)); assertEquals(VIBRATION_INTENSITY_LOW, mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); @@ -587,7 +605,7 @@ public class VibrationSettingsTest { public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() { setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is off, fallback to default value. assertEquals(VIBRATION_INTENSITY_MEDIUM, @@ -596,7 +614,7 @@ public class VibrationSettingsTest { mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is on, fallback to that value. @@ -633,7 +651,7 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(UID, new VibrationAttributes.Builder() .setUsage(usage) - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .setFlags(flags) .build())); } @@ -654,18 +672,19 @@ public class VibrationSettingsTest { Settings.System.putStringForUser( mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } private void setRingerMode(int ringerMode) { mAudioManager.setRingerModeInternal(ringerMode); assertEquals(ringerMode, mAudioManager.getRingerModeInternal()); + mVibrationSettings.update(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 92736c517782d..9c72ce22c857c 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -672,6 +672,42 @@ public class VibratorManagerServiceTest { eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); } + @Test + public void vibrate_withVibrationAttributesEnforceFreshSettings_refreshesVibrationSettings() + throws Exception { + mockVibrators(0); + mVibratorProviders.get(0).setSupportedEffects(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_TICK); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + VibratorManagerService service = createSystemReadyService(); + + VibrationAttributes enforceFreshAttrs = new VibrationAttributes.Builder() + .setUsage(VibrationAttributes.USAGE_NOTIFICATION) + .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE) + .build(); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), NOTIFICATION_ATTRS); + // VibrationThread will start this vibration async, so wait before vibrating a second time. + assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 0, + service, TEST_TIMEOUT_MILLIS)); + + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), enforceFreshAttrs); + // VibrationThread will start this vibration async, so wait before checking. + assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 1, + service, TEST_TIMEOUT_MILLIS)); + + assertEquals( + Arrays.asList( + expectedPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_STRONG), + expectedPrebaked(VibrationEffect.EFFECT_TICK, + VibrationEffect.EFFECT_STRENGTH_LIGHT)), + mVibratorProviders.get(0).getAllEffectSegments()); + } + @Test public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() { VibratorManagerService service = createSystemReadyService(); @@ -1280,7 +1316,11 @@ public class VibratorManagerServiceTest { } private VibrationEffectSegment expectedPrebaked(int effectId) { - return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + } + + private VibrationEffectSegment expectedPrebaked(int effectId, int effectStrength) { + return new PrebakedSegment(effectId, false, effectStrength); } private void mockCapabilities(long... capabilities) {