From 6e075783ee90c4e539e98e0335c97772d0a49812 Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Tue, 30 Mar 2021 18:32:29 +0000 Subject: [PATCH] Add Vibrator.cancel method with usage filters Add a new hidden API to Vibrator and VibratorManager that allows vibrations to be cancelled based on their usage attribute. The new API is used by the NotificationManagerService on volume button press to only cancel ongoing attentional haptics, triggered for alarm, notification or ringtone. Fix: 182440404 Test: VibratorManagerTest & BuzzBeepBlinkTest Change-Id: I4bf55928cf3b7dd79f66d53d570001a5205ce2d0 --- .../hardware/input/InputDeviceVibrator.java | 5 ++ .../input/InputDeviceVibratorManager.java | 5 ++ .../android/os/IVibratorManagerService.aidl | 2 +- core/java/android/os/NullVibrator.java | 4 ++ core/java/android/os/SystemVibrator.java | 9 +++ .../android/os/SystemVibratorManager.java | 67 ++++++++----------- core/java/android/os/Vibrator.java | 10 +++ core/java/android/os/VibratorManager.java | 10 +++ .../NotificationManagerService.java | 46 +++---------- .../vibrator/VibratorManagerService.java | 44 ++++++++++-- .../android/server/vibrator/FakeVibrator.java | 4 ++ .../vibrator/VibratorManagerServiceTest.java | 35 ++++++++-- .../notification/BuzzBeepBlinkTest.java | 8 ++- .../VibratorManagerServicePermissionTest.java | 2 +- 14 files changed, 159 insertions(+), 92 deletions(-) diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index a4817ae27fa5d..1ab183626d2e6 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -175,4 +175,9 @@ final class InputDeviceVibrator extends Vibrator { public void cancel() { mInputManager.cancelVibrate(mDeviceId, mToken); } + + @Override + public void cancel(int usageFilter) { + cancel(); + } } diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java index ed0efffbb3469..7caff7209cc56 100644 --- a/core/java/android/hardware/input/InputDeviceVibratorManager.java +++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java @@ -134,4 +134,9 @@ public class InputDeviceVibratorManager extends VibratorManager public void cancel() { mInputManager.cancelVibrate(mDeviceId, mToken); } + + @Override + public void cancel(int usageFilter) { + cancel(); + } } diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index c58cc4f9988fd..a0d6ce1ba1081 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -32,5 +32,5 @@ interface IVibratorManagerService { in CombinedVibration vibration, in VibrationAttributes attributes); void vibrate(int uid, String opPkg, in CombinedVibration vibration, in VibrationAttributes attributes, String reason, IBinder token); - void cancelVibrate(IBinder token); + void cancelVibrate(int usageFilter, IBinder token); } diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java index 6bb016519a847..7859b5cfe5dff 100644 --- a/core/java/android/os/NullVibrator.java +++ b/core/java/android/os/NullVibrator.java @@ -54,4 +54,8 @@ public class NullVibrator extends Vibrator { @Override public void cancel() { } + + @Override + public void cancel(int usageFilter) { + } } diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 2e8ecb59b0d35..70808594225fb 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -256,6 +256,15 @@ public class SystemVibrator extends Vibrator { mVibratorManager.cancel(); } + @Override + public void cancel(int usageFilter) { + if (mVibratorManager == null) { + Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); + return; + } + mVibratorManager.cancel(usageFilter); + } + /** * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} * that were left registered to vibrators after failures to register them to all vibrators. diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index 84a1016e33640..ba86c6f9ec1aa 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -146,12 +146,21 @@ public class SystemVibratorManager extends VibratorManager { @Override public void cancel() { + cancelVibration(/* usageFilter= */ -1); + } + + @Override + public void cancel(int usageFilter) { + cancelVibration(usageFilter); + } + + private void cancelVibration(int usageFilter) { if (mService == null) { Log.w(TAG, "Failed to cancel vibration; no vibrator manager service."); return; } try { - mService.cancelVibrate(mToken); + mService.cancelVibrate(usageFilter, mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to cancel vibration.", e); } @@ -232,54 +241,32 @@ public class SystemVibratorManager extends VibratorManager { @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) { - if (mService == null) { - Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId() - + "; no vibrator manager service."); - return false; - } - try { - VibrationAttributes attr = new VibrationAttributes.Builder( - attributes, effect).build(); - CombinedVibration combined = CombinedVibration.startParallel() - .addVibrator(mVibratorInfo.getId(), effect) - .combine(); - return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr); - } catch (RemoteException e) { - Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()); - } - return false; + VibrationAttributes attr = new VibrationAttributes.Builder( + attributes, effect).build(); + CombinedVibration combined = CombinedVibration.startParallel() + .addVibrator(mVibratorInfo.getId(), effect) + .combine(); + return SystemVibratorManager.this.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, + attr); } @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason, @NonNull VibrationAttributes attributes) { - if (mService == null) { - Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId() - + "; no vibrator manager service."); - return; - } - try { - CombinedVibration combined = CombinedVibration.startParallel() - .addVibrator(mVibratorInfo.getId(), vibe) - .combine(); - mService.vibrate(uid, opPkg, combined, attributes, reason, mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to vibrate.", e); - } + CombinedVibration combined = CombinedVibration.startParallel() + .addVibrator(mVibratorInfo.getId(), vibe) + .combine(); + SystemVibratorManager.this.vibrate(uid, opPkg, combined, reason, attributes); } @Override public void cancel() { - if (mService == null) { - Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId() - + "; no vibrator manager service."); - return; - } - try { - mService.cancelVibrate(mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e); - } + SystemVibratorManager.this.cancel(); + } + + @Override + public void cancel(int usageFilter) { + SystemVibratorManager.this.cancel(usageFilter); } @Override diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index a0f70c8fa5260..54cb83f7caf1f 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -493,6 +493,16 @@ public abstract class Vibrator { @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + /** + * Cancel specific types of ongoing vibrations. + * + * @param usageFilter The type of vibration to be cancelled, represented as a bitwise + * combination of {@link VibrationAttributes.Usage} values. + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void cancel(int usageFilter); + /** * Check whether the vibrator is vibrating. * diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index 7c911160dfa64..01cece39b9228 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -136,4 +136,14 @@ public abstract class VibratorManager { */ @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + + /** + * Cancel specific types of ongoing vibrations. + * + * @param usageFilter The type of vibration to be cancelled, represented as a bitwise + * combination of {@link VibrationAttributes.Usage} values. + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void cancel(int usageFilter); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bd442bdf9d636..21b93a74655fc 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -212,6 +212,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; @@ -1076,23 +1077,8 @@ public class NotificationManagerService extends SystemService { (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; if (disableNotificationEffects(null) != null) { // cancel whatever's going on - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - final long identity2 = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity2); - } + clearSoundLocked(); + clearVibrateLocked(); } } } @@ -1582,7 +1568,10 @@ public class NotificationManagerService extends SystemService { mVibrateNotificationKey = null; final long identity = Binder.clearCallingIdentity(); try { - mVibrator.cancel(); + // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages). + int usageFilter = + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; + mVibrator.cancel(usageFilter); } finally { Binder.restoreCallingIdentity(identity); } @@ -8301,29 +8290,12 @@ public class NotificationManagerService extends SystemService { // sound if (canceledKey.equals(mSoundNotificationKey)) { - mSoundNotificationKey = null; - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } + clearSoundLocked(); } // vibrate if (canceledKey.equals(mVibrateNotificationKey)) { - mVibrateNotificationKey = null; - final long identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } - finally { - Binder.restoreCallingIdentity(identity); - } + clearVibrateLocked(); } // light diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 5d2b1b169c4e1..06a5077bec829 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -383,7 +383,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call - public void cancelVibrate(IBinder token) { + public void cancelVibrate(int usageFilter, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate"); try { mContext.enforceCallingOrSelfPermission( @@ -392,16 +392,24 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { synchronized (mLock) { if (DEBUG) { - Slog.d(TAG, "Canceling vibration."); + Slog.d(TAG, "Canceling vibration"); } final long ident = Binder.clearCallingIdentity(); try { - mNextVibration = null; + if (mNextVibration != null + && shouldCancelVibration(mNextVibration.getVibration(), + usageFilter, token)) { + mNextVibration = null; + } if (mCurrentVibration != null - && mCurrentVibration.getVibration().token == token) { + && shouldCancelVibration(mCurrentVibration.getVibration(), + usageFilter, token)) { mCurrentVibration.cancel(); } - if (mCurrentExternalVibration != null) { + if (mCurrentExternalVibration != null + && shouldCancelVibration( + mCurrentExternalVibration.externalVibration.getVibrationAttributes(), + usageFilter)) { mCurrentExternalVibration.end(Vibration.Status.CANCELLED); mVibratorManagerRecords.record(mCurrentExternalVibration); mCurrentExternalVibration.externalVibration.mute(); @@ -692,6 +700,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return null; } + /** + * Return true if the vibration has the same token and usage belongs to given usage class. + * + * @param vib The ongoing or pending vibration to be cancelled. + * @param usageFilter The vibration usages to be cancelled, any bitwise combination of + * VibrationAttributes.USAGE_* values. + * @param token The binder token to identify the vibration origin. Only vibrations + * started with the same token can be cancelled with it. + */ + private boolean shouldCancelVibration(Vibration vib, int usageFilter, IBinder token) { + return (vib.token == token) && shouldCancelVibration(vib.attrs, usageFilter); + } + + /** + * Return true if the external vibration usage belongs to given usage class. + * + * @param attrs The attributes of an ongoing or pending vibration to be cancelled. + * @param usageFilter The vibration usages to be cancelled, any bitwise combination of + * VibrationAttributes.USAGE_* values. + */ + private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) { + return (usageFilter & attrs.getUsage()) == attrs.getUsage(); + } + /** * Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and * {@code attrs}. This will return one of the AppOpsManager.MODE_*. @@ -1501,7 +1533,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } private int runCancel() { - cancelVibrate(mToken); + cancelVibrate(/* usageFilter= */ -1, mToken); return 0; } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java index 014bfd2d40e4a..e739a7658a959 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java @@ -76,4 +76,8 @@ final class FakeVibrator extends Vibrator { @Override public void cancel() { } + + @Override + public void cancel(int usageFilter) { + } } 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 e367b7448299f..b4c1de1c68f54 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -234,7 +234,7 @@ public class VibratorManagerServiceTest { CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); - service.cancelVibrate(service); + service.cancelVibrate(/* usageFilter= */ -1, service); assertTrue(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); @@ -880,17 +880,42 @@ public class VibratorManagerServiceTest { } @Test - public void cancelVibrate_stopsVibrating() throws Exception { + public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); - service.cancelVibrate(service); + service.cancelVibrate(/* usageFilter= */ -1, service); assertFalse(service.isVibrating(1)); - vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS); + vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); - service.cancelVibrate(service); + service.cancelVibrate(/* usageFilter= */ -1, service); + assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + } + + @Test + public void cancelVibrate_withFilter_onlyCancelsVibrationWithFilteredUsage() throws Exception { + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + + vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS); + assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + + // Vibration is not cancelled with a different usage. + service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service); + assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50)); + + // Vibration is not cancelled with a different usage class used as filter. + service.cancelVibrate( + VibrationAttributes.USAGE_CLASS_FEEDBACK | ~VibrationAttributes.USAGE_CLASS_MASK, + service); + assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50)); + + // Vibration is cancelled with usage class as filter. + service.cancelVibrate( + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK, + service); assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ff881748cfeaa..00eb0f2847196 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -64,6 +64,7 @@ import android.os.Handler; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; @@ -444,11 +445,14 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } private void verifyStopVibrate() { - verify(mVibrator, times(1)).cancel(); + int alarmClassUsageFilter = + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; + verify(mVibrator, times(1)).cancel(eq(alarmClassUsageFilter)); } - private void verifyNeverStopVibrate() throws RemoteException { + private void verifyNeverStopVibrate() { verify(mVibrator, never()).cancel(); + verify(mVibrator, never()).cancel(anyInt()); } private void verifyNeverLights() { diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index 7cd2c23162fba..e0f3f03e9cb70 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -140,7 +140,7 @@ public class VibratorManagerServicePermissionTest { @Test public void testCancelVibrateFails() throws RemoteException { expectSecurityException("VIBRATE"); - mVibratorService.cancelVibrate(new Binder()); + mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder()); } private void expectSecurityException(String expectedPermission) {