Loading core/java/android/os/vibrator/flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -31,3 +31,14 @@ flag { description: "Enables the adaptive haptics feature" bug: "305961689" } flag { namespace: "haptics" name: "cancel_by_appops" description: "Cancels ongoing vibrations when the appops mode changes to disallow them" bug: "230745615" is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } } core/proto/android/server/vibrator/vibratormanagerservice.proto +1 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,7 @@ message VibrationProto { IGNORED_FROM_VIRTUAL_DEVICE = 26; IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; CANCELLED_BY_APP_OPS = 29; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } Loading services/core/java/com/android/server/vibrator/Vibration.java +1 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ abstract class Vibration { CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER), CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON), CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED), CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS), IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS), IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING), IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING), Loading services/core/java/com/android/server/vibrator/VibratorManagerService.java +33 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } }; @VisibleForTesting final AppOpsManager.OnOpChangedInternalListener mAppOpsChangeListener = new AppOpsManager.OnOpChangedInternalListener() { @Override public void onOpChanged(int op, String packageName) { if (op != AppOpsManager.OP_VIBRATE) { return; } synchronized (mLock) { if (shouldCancelAppOpModeChangedLocked(mNextVibration)) { clearNextVibrationLocked( new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS)); } if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) { mCurrentVibration.notifyCancelled(new Vibration.EndInfo( Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false); } } } }; static native long nativeInit(OnSyncedVibrationCompleteListener listener); static native long nativeGetFinalizer(); Loading Loading @@ -238,6 +259,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mBatteryStatsService = injector.getBatteryStatsService(); mAppOps = mContext.getSystemService(AppOpsManager.class); if (Flags.cancelByAppops()) { mAppOps.startWatchingMode(AppOpsManager.OP_VIBRATE, null, mAppOpsChangeListener); } PowerManager pm = context.getSystemService(PowerManager.class); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); Loading Loading @@ -1389,6 +1413,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { vib.stats.getCreateUptimeMillis()); } @GuardedBy("mLock") private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) { if (conductor == null) { return false; } return checkAppOpModeLocked(conductor.getVibration().callerInfo) != AppOpsManager.MODE_ALLOWED; } @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { Loading services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +28 −0 Original line number Diff line number Diff line Loading @@ -780,6 +780,34 @@ public class VibratorManagerServiceTest { eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString()); } @Test @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS) public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); var vib = vibrate(service, VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString())) .thenReturn(AppOpsManager.MODE_IGNORED); service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null); assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS)); var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class); verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS)) .writeVibrationReportedAsync(statsInfoCaptor.capture()); VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0); assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(), touchMetrics.status); } @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { VibratorManagerService service = createSystemReadyService(); Loading Loading
core/java/android/os/vibrator/flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -31,3 +31,14 @@ flag { description: "Enables the adaptive haptics feature" bug: "305961689" } flag { namespace: "haptics" name: "cancel_by_appops" description: "Cancels ongoing vibrations when the appops mode changes to disallow them" bug: "230745615" is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } }
core/proto/android/server/vibrator/vibratormanagerservice.proto +1 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,7 @@ message VibrationProto { IGNORED_FROM_VIRTUAL_DEVICE = 26; IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; CANCELLED_BY_APP_OPS = 29; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } Loading
services/core/java/com/android/server/vibrator/Vibration.java +1 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ abstract class Vibration { CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER), CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON), CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED), CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS), IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS), IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING), IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING), Loading
services/core/java/com/android/server/vibrator/VibratorManagerService.java +33 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } }; @VisibleForTesting final AppOpsManager.OnOpChangedInternalListener mAppOpsChangeListener = new AppOpsManager.OnOpChangedInternalListener() { @Override public void onOpChanged(int op, String packageName) { if (op != AppOpsManager.OP_VIBRATE) { return; } synchronized (mLock) { if (shouldCancelAppOpModeChangedLocked(mNextVibration)) { clearNextVibrationLocked( new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS)); } if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) { mCurrentVibration.notifyCancelled(new Vibration.EndInfo( Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false); } } } }; static native long nativeInit(OnSyncedVibrationCompleteListener listener); static native long nativeGetFinalizer(); Loading Loading @@ -238,6 +259,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mBatteryStatsService = injector.getBatteryStatsService(); mAppOps = mContext.getSystemService(AppOpsManager.class); if (Flags.cancelByAppops()) { mAppOps.startWatchingMode(AppOpsManager.OP_VIBRATE, null, mAppOpsChangeListener); } PowerManager pm = context.getSystemService(PowerManager.class); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); Loading Loading @@ -1389,6 +1413,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { vib.stats.getCreateUptimeMillis()); } @GuardedBy("mLock") private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) { if (conductor == null) { return false; } return checkAppOpModeLocked(conductor.getVibration().callerInfo) != AppOpsManager.MODE_ALLOWED; } @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { Loading
services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +28 −0 Original line number Diff line number Diff line Loading @@ -780,6 +780,34 @@ public class VibratorManagerServiceTest { eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString()); } @Test @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS) public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); var vib = vibrate(service, VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString())) .thenReturn(AppOpsManager.MODE_IGNORED); service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null); assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS)); var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class); verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS)) .writeVibrationReportedAsync(statsInfoCaptor.capture()); VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0); assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(), touchMetrics.status); } @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { VibratorManagerService service = createSystemReadyService(); Loading