Merge "Fix external vibration behavior on system update" into main
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);