Fix external vibration behavior on system update
Fix vibrator service to consistently handle ongoing vibrations.
This means that system updates like user settings changes,
screen off events, ringer mode and battery-saver mode updates
will also affect ongoing external vibrations.
Fix: 372241975
Test: VibratorManagerServiceTest
Flag: android.os.vibrator.fix_external_vibration_system_update_aware
Change-Id: Ief72ff646503b0983ea20473a87b44577375f1d3
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 0615578..414f2749 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -155,3 +155,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "haptics"
+ name: "fix_external_vibration_system_update_aware"
+ description: "Fix the audio-coupled haptics handling of system updates."
+ bug: "372241975"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 3f5fc33..ce91e63 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1015,9 +1015,12 @@
updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
}
- // TODO(b/372241975): investigate why external vibrations were not handled here before
- if (mCurrentSession == null
- || (mCurrentSession instanceof ExternalVibrationSession)) {
+ if (mCurrentSession == null) {
+ return;
+ }
+
+ if (!Flags.fixExternalVibrationSystemUpdateAware()
+ && (mCurrentSession instanceof ExternalVibrationSession)) {
return;
}
@@ -1025,7 +1028,7 @@
if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
- + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
+ + (ignoreStatus == null ? "input devices changed" : ignoreStatus));
}
mCurrentSession.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
@@ -2334,14 +2337,22 @@
@GuardedBy("mLock")
private void maybeClearCurrentAndNextSessionsLocked(
Predicate<VibrationSession> shouldEndSessionPredicate, Status endStatus) {
- // TODO(b/372241975): investigate why external vibrations were not handled here before
- if (!(mNextSession instanceof ExternalVibrationSession)
- && shouldEndSessionPredicate.test(mNextSession)) {
- clearNextSessionLocked(endStatus);
- }
- if (!(mCurrentSession instanceof ExternalVibrationSession)
- && shouldEndSessionPredicate.test(mCurrentSession)) {
- mCurrentSession.requestEnd(endStatus);
+ if (Flags.fixExternalVibrationSystemUpdateAware()) {
+ if (shouldEndSessionPredicate.test(mNextSession)) {
+ clearNextSessionLocked(endStatus);
+ }
+ if (shouldEndSessionPredicate.test(mCurrentSession)) {
+ mCurrentSession.requestEnd(endStatus);
+ }
+ } else {
+ if (!(mNextSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mNextSession)) {
+ clearNextSessionLocked(endStatus);
+ }
+ if (!(mCurrentSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mCurrentSession)) {
+ mCurrentSession.requestEnd(endStatus);
+ }
}
}
@@ -2535,6 +2546,9 @@
Slog.d(TAG, "Stopping external vibration: " + vib);
}
mCurrentSession.requestEnd(Status.FINISHED);
+ } else if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on external vibration stop."
+ + " currentSession=" + mCurrentSession + ", received=" + vib);
}
}
} finally {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index b248218..132a324 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -147,6 +147,9 @@
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build();
private static final AudioAttributes AUDIO_NOTIFICATION_ATTRS =
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
+ private static final AudioAttributes AUDIO_HAPTIC_FEEDBACK_ATTRS =
+ new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build();
private static final VibrationAttributes ALARM_ATTRS =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
@@ -2674,7 +2677,8 @@
}
@Test
- public void onExternalVibration_thenDeniedAppOps_doNotCancelVibration() throws Throwable {
+ @DisableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_legacyDeniedAppOps_doNotCancelVibration() throws Throwable {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
@@ -2697,7 +2701,32 @@
}
@Test
- public void onExternalVibration_thenPowerModeChanges_doNotCancelVibration() throws Exception {
+ @EnableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_thenDeniedAppOps_cancelVibration() throws Throwable {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString()))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+ service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
+
+ verify(externalVibrationControllerMock).mute();
+ }
+
+ @Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_legacyPowerModeChanges_doNotCancelVibration() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
createSystemReadyService();
@@ -2705,7 +2734,7 @@
IExternalVibrationController externalVibrationControllerMock =
mock(IExternalVibrationController.class);
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
- AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ AUDIO_HAPTIC_FEEDBACK_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
externalVibration);
@@ -2717,7 +2746,29 @@
}
@Test
- public void onExternalVibration_thenSettingsChange_doNotCancelVibration() throws Exception {
+ @EnableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_thenPowerModeChanges_cancelVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_HAPTIC_FEEDBACK_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ verify(externalVibrationControllerMock).mute();
+ }
+
+ @Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_legacySettingsChange_doNotCancelVibration() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
@@ -2739,7 +2790,31 @@
}
@Test
- public void onExternalVibration_thenScreenTurnsOff_doNotCancelVibration() throws Throwable {
+ @EnableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_thenSettingsChange_cancelVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+ service.mVibrationSettings.mSettingObserver.onChange(false);
+ service.updateServiceState();
+
+ verify(externalVibrationControllerMock).mute();
+ }
+
+ @Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_legacyScreenTurnsOff_doNotCancelVibration() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
@@ -2759,7 +2834,30 @@
}
@Test
- public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
+ @EnableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_thenScreenTurnsOff_cancelVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_SCREEN_OFF));
+
+ verify(externalVibrationControllerMock).mute();
+ }
+
+ @Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_legacyFgUserRequestsMute_doNotCancelVibration()
+ throws Exception {
assumeTrue(UserManagerInternal.shouldShowNotificationForBackgroundUserSounds());
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2781,6 +2879,29 @@
}
@Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_FIX_EXTERNAL_VIBRATION_SYSTEM_UPDATE_AWARE)
+ public void onExternalVibration_thenFgUserRequestsMute_cancelVibration() throws Exception {
+ assumeTrue(UserManagerInternal.shouldShowNotificationForBackgroundUserSounds());
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ IExternalVibrationController externalVibrationControllerMock =
+ mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+ service.mIntentReceiver.onReceive(mContextSpy, new Intent(
+ BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
+
+ verify(externalVibrationControllerMock).mute();
+ }
+
+ @Test
@DisableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void startVibrationSession_withoutFeatureFlag_throwsException() throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);