Loading services/core/java/com/android/server/vibrator/VibratorManagerService.java +20 −12 Original line number Diff line number Diff line Loading @@ -1301,7 +1301,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */ private final class ExternalVibratorService extends IExternalVibratorService.Stub { @VisibleForTesting final class ExternalVibratorService extends IExternalVibratorService.Stub { ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; @Override Loading Loading @@ -1332,6 +1333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return vibHolder.scale; } ExternalVibrationHolder cancelingExternalVibration = null; VibrationThread cancelingVibration = null; int scale; synchronized (mLock) { Loading @@ -1350,16 +1352,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { cancelingVibration = mCurrentVibration; } } else { endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); } // At this point we either have an externally controlled vibration playing, or // no vibration playing. Since the interface defines that only one externally // controlled vibration can play at a time, by returning something other than // SCALE_MUTE from this function we can be assured that if we are currently // playing vibration, it will be muted in favor of the new vibration. // At this point we have an externally controlled vibration playing already. // Since the interface defines that only one externally controlled vibration can // play at a time, we need to first mute the ongoing vibration and then return // a scale from this function for the new one. Ee can be assured that the // ongoing it will be muted in favor of the new vibration. // // Note that this doesn't support multiple concurrent external controls, as we // would need to mute the old one still if it came from a different controller. mCurrentExternalVibration.externalVibration.mute(); endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); cancelingExternalVibration = mCurrentExternalVibration; } mCurrentExternalVibration = new ExternalVibrationHolder(vib); mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); vib.linkToDeath(mCurrentExternalDeathRecipient); Loading @@ -1376,10 +1380,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + "external control", e); } } if (cancelingExternalVibration == null) { // We only need to set external control if it was not already set by another // external vibration. if (DEBUG) { Slog.d(TAG, "Vibrator going under external control."); } setExternalControl(true); } if (DEBUG) { Slog.e(TAG, "Playing external vibration: " + vib); } Loading services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +7 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ final class FakeVibratorControllerProvider { private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>(); private final List<Integer> mBraking = new ArrayList<>(); private final List<Float> mAmplitudes = new ArrayList<>(); private final List<Boolean> mExternalControlStates = new ArrayList<>(); private final Handler mHandler; private final FakeNativeWrapper mNativeWrapper; Loading Loading @@ -139,6 +140,7 @@ final class FakeVibratorControllerProvider { @Override public void setExternalControl(boolean enabled) { mExternalControlStates.add(enabled); } @Override Loading Loading @@ -301,6 +303,11 @@ final class FakeVibratorControllerProvider { return new ArrayList<>(mEffectSegments); } /** Return list of states set for external control to the fake vibrator hardware. */ public List<Boolean> getExternalControlStates() { return mExternalControlStates; } /** * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if * missing or disabled. Loading services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +73 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AppOpsManager; Loading @@ -50,8 +51,11 @@ import android.hardware.vibrator.IVibratorManager; import android.media.AudioAttributes; import android.media.AudioManager; import android.os.CombinedVibration; import android.os.ExternalVibration; import android.os.Handler; import android.os.IBinder; import android.os.IExternalVibrationController; import android.os.IExternalVibratorService; import android.os.IVibratorStateListener; import android.os.Looper; import android.os.PowerManager; Loading Loading @@ -108,6 +112,8 @@ public class VibratorManagerServiceTest { private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() .setBatterySaverEnabled(true).build(); private static final AudioAttributes AUDIO_ATTRS = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build(); private static final VibrationAttributes ALARM_ATTRS = new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS = Loading Loading @@ -136,6 +142,7 @@ public class VibratorManagerServiceTest { private TestLooper mTestLooper; private FakeVibrator mVibrator; private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private VibratorManagerService.ExternalVibratorService mExternalVibratorService; @Before public void setUp() throws Exception { Loading Loading @@ -208,6 +215,9 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { Object serviceInstance = service; mExternalVibratorService = (VibratorManagerService.ExternalVibratorService) serviceInstance; } }); } Loading Loading @@ -967,6 +977,69 @@ public class VibratorManagerServiceTest { assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); } @Test public void onExternalVibration_setsExternalControl() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, mock(IExternalVibrationController.class)); int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); mExternalVibratorService.onExternalVibrationStop(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); assertEquals(Arrays.asList(true, false), mVibratorProviders.get(1).getExternalControlStates()); } @Test public void onExternalVibration_withOngoingExternalVibration_mutesPreviousVibration() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); IExternalVibrationController firstController = mock(IExternalVibrationController.class); IExternalVibrationController secondController = mock(IExternalVibrationController.class); ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, firstController); int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration); ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, secondController); int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration); assertEquals(IExternalVibratorService.SCALE_NONE, firstScale); assertEquals(IExternalVibratorService.SCALE_NONE, secondScale); verify(firstController).mute(); verifyNoMoreInteractions(secondController); // Set external control called only once. assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } @Test public void onExternalVibration_withOngoingVibration_cancelsOngoingVibrationImmediately() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL, IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100); vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, mock(IExternalVibrationController.class)); int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); // Vibration is cancelled. assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } Loading Loading
services/core/java/com/android/server/vibrator/VibratorManagerService.java +20 −12 Original line number Diff line number Diff line Loading @@ -1301,7 +1301,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */ private final class ExternalVibratorService extends IExternalVibratorService.Stub { @VisibleForTesting final class ExternalVibratorService extends IExternalVibratorService.Stub { ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; @Override Loading Loading @@ -1332,6 +1333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return vibHolder.scale; } ExternalVibrationHolder cancelingExternalVibration = null; VibrationThread cancelingVibration = null; int scale; synchronized (mLock) { Loading @@ -1350,16 +1352,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { cancelingVibration = mCurrentVibration; } } else { endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); } // At this point we either have an externally controlled vibration playing, or // no vibration playing. Since the interface defines that only one externally // controlled vibration can play at a time, by returning something other than // SCALE_MUTE from this function we can be assured that if we are currently // playing vibration, it will be muted in favor of the new vibration. // At this point we have an externally controlled vibration playing already. // Since the interface defines that only one externally controlled vibration can // play at a time, we need to first mute the ongoing vibration and then return // a scale from this function for the new one. Ee can be assured that the // ongoing it will be muted in favor of the new vibration. // // Note that this doesn't support multiple concurrent external controls, as we // would need to mute the old one still if it came from a different controller. mCurrentExternalVibration.externalVibration.mute(); endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); cancelingExternalVibration = mCurrentExternalVibration; } mCurrentExternalVibration = new ExternalVibrationHolder(vib); mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); vib.linkToDeath(mCurrentExternalDeathRecipient); Loading @@ -1376,10 +1380,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + "external control", e); } } if (cancelingExternalVibration == null) { // We only need to set external control if it was not already set by another // external vibration. if (DEBUG) { Slog.d(TAG, "Vibrator going under external control."); } setExternalControl(true); } if (DEBUG) { Slog.e(TAG, "Playing external vibration: " + vib); } Loading
services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +7 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ final class FakeVibratorControllerProvider { private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>(); private final List<Integer> mBraking = new ArrayList<>(); private final List<Float> mAmplitudes = new ArrayList<>(); private final List<Boolean> mExternalControlStates = new ArrayList<>(); private final Handler mHandler; private final FakeNativeWrapper mNativeWrapper; Loading Loading @@ -139,6 +140,7 @@ final class FakeVibratorControllerProvider { @Override public void setExternalControl(boolean enabled) { mExternalControlStates.add(enabled); } @Override Loading Loading @@ -301,6 +303,11 @@ final class FakeVibratorControllerProvider { return new ArrayList<>(mEffectSegments); } /** Return list of states set for external control to the fake vibrator hardware. */ public List<Boolean> getExternalControlStates() { return mExternalControlStates; } /** * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if * missing or disabled. Loading
services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +73 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AppOpsManager; Loading @@ -50,8 +51,11 @@ import android.hardware.vibrator.IVibratorManager; import android.media.AudioAttributes; import android.media.AudioManager; import android.os.CombinedVibration; import android.os.ExternalVibration; import android.os.Handler; import android.os.IBinder; import android.os.IExternalVibrationController; import android.os.IExternalVibratorService; import android.os.IVibratorStateListener; import android.os.Looper; import android.os.PowerManager; Loading Loading @@ -108,6 +112,8 @@ public class VibratorManagerServiceTest { private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() .setBatterySaverEnabled(true).build(); private static final AudioAttributes AUDIO_ATTRS = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build(); private static final VibrationAttributes ALARM_ATTRS = new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS = Loading Loading @@ -136,6 +142,7 @@ public class VibratorManagerServiceTest { private TestLooper mTestLooper; private FakeVibrator mVibrator; private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private VibratorManagerService.ExternalVibratorService mExternalVibratorService; @Before public void setUp() throws Exception { Loading Loading @@ -208,6 +215,9 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { Object serviceInstance = service; mExternalVibratorService = (VibratorManagerService.ExternalVibratorService) serviceInstance; } }); } Loading Loading @@ -967,6 +977,69 @@ public class VibratorManagerServiceTest { assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); } @Test public void onExternalVibration_setsExternalControl() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, mock(IExternalVibrationController.class)); int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); mExternalVibratorService.onExternalVibrationStop(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); assertEquals(Arrays.asList(true, false), mVibratorProviders.get(1).getExternalControlStates()); } @Test public void onExternalVibration_withOngoingExternalVibration_mutesPreviousVibration() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); IExternalVibrationController firstController = mock(IExternalVibrationController.class); IExternalVibrationController secondController = mock(IExternalVibrationController.class); ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, firstController); int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration); ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, secondController); int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration); assertEquals(IExternalVibratorService.SCALE_NONE, firstScale); assertEquals(IExternalVibratorService.SCALE_NONE, secondScale); verify(firstController).mute(); verifyNoMoreInteractions(secondController); // Set external control called only once. assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } @Test public void onExternalVibration_withOngoingVibration_cancelsOngoingVibrationImmediately() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL, IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100); vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, mock(IExternalVibrationController.class)); int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); // Vibration is cancelled. assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } Loading