Fix concurrent external vibrations on ExternalVibratorService
The ongoing external vibration should be muted via IExternalVibrationController and the service should not set external control on the vibrator more than once. Fix service to mute ongoing external vibration at the time it's cancelled and to skip calls to setExternalControl when it's already set. Fix: 193127756 Test: VibratorManagerServiceTest Change-Id: If61f5fd2c41dc6d15655d9ce537c858caaf530c6
This commit is contained in:
@@ -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
|
||||
@@ -1332,6 +1333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
return vibHolder.scale;
|
||||
}
|
||||
|
||||
ExternalVibrationHolder cancelingExternalVibration = null;
|
||||
VibrationThread cancelingVibration = null;
|
||||
int scale;
|
||||
synchronized (mLock) {
|
||||
@@ -1350,16 +1352,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
cancelingVibration = mCurrentVibration;
|
||||
}
|
||||
} else {
|
||||
// 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;
|
||||
}
|
||||
// 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.
|
||||
//
|
||||
// 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 = new ExternalVibrationHolder(vib);
|
||||
mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
|
||||
vib.linkToDeath(mCurrentExternalDeathRecipient);
|
||||
@@ -1376,10 +1380,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
+ "external control", e);
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Vibrator going under external control.");
|
||||
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);
|
||||
}
|
||||
setExternalControl(true);
|
||||
if (DEBUG) {
|
||||
Slog.e(TAG, "Playing external vibration: " + vib);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -139,6 +140,7 @@ final class FakeVibratorControllerProvider {
|
||||
|
||||
@Override
|
||||
public void setExternalControl(boolean enabled) {
|
||||
mExternalControlStates.add(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 =
|
||||
@@ -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 {
|
||||
@@ -208,6 +215,9 @@ public class VibratorManagerServiceTest {
|
||||
|
||||
@Override
|
||||
void addService(String name, IBinder service) {
|
||||
Object serviceInstance = service;
|
||||
mExternalVibratorService =
|
||||
(VibratorManagerService.ExternalVibratorService) serviceInstance;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user