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) {