Merge "Fix concurrent external vibrations on ExternalVibratorService" into sc-dev am: 1dd6f20e3b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15243095 Change-Id: If55d15b631ecd8f0da0afba6d2388db9d3405f06
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. */
|
/** 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;
|
ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1332,6 +1333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
return vibHolder.scale;
|
return vibHolder.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExternalVibrationHolder cancelingExternalVibration = null;
|
||||||
VibrationThread cancelingVibration = null;
|
VibrationThread cancelingVibration = null;
|
||||||
int scale;
|
int scale;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
@@ -1350,16 +1352,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
cancelingVibration = mCurrentVibration;
|
cancelingVibration = mCurrentVibration;
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
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);
|
mCurrentExternalVibration = new ExternalVibrationHolder(vib);
|
||||||
mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
|
mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
|
||||||
vib.linkToDeath(mCurrentExternalDeathRecipient);
|
vib.linkToDeath(mCurrentExternalDeathRecipient);
|
||||||
@@ -1376,10 +1380,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
+ "external control", e);
|
+ "external control", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (cancelingExternalVibration == null) {
|
||||||
Slog.d(TAG, "Vibrator going under external control.");
|
// 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) {
|
if (DEBUG) {
|
||||||
Slog.e(TAG, "Playing external vibration: " + vib);
|
Slog.e(TAG, "Playing external vibration: " + vib);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
|
private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
|
||||||
private final List<Integer> mBraking = new ArrayList<>();
|
private final List<Integer> mBraking = new ArrayList<>();
|
||||||
private final List<Float> mAmplitudes = new ArrayList<>();
|
private final List<Float> mAmplitudes = new ArrayList<>();
|
||||||
|
private final List<Boolean> mExternalControlStates = new ArrayList<>();
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final FakeNativeWrapper mNativeWrapper;
|
private final FakeNativeWrapper mNativeWrapper;
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setExternalControl(boolean enabled) {
|
public void setExternalControl(boolean enabled) {
|
||||||
|
mExternalControlStates.add(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -301,6 +303,11 @@ final class FakeVibratorControllerProvider {
|
|||||||
return new ArrayList<>(mEffectSegments);
|
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
|
* Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
|
||||||
* missing or disabled.
|
* missing or disabled.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
@@ -50,8 +51,11 @@ import android.hardware.vibrator.IVibratorManager;
|
|||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.CombinedVibration;
|
import android.os.CombinedVibration;
|
||||||
|
import android.os.ExternalVibration;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.IExternalVibrationController;
|
||||||
|
import android.os.IExternalVibratorService;
|
||||||
import android.os.IVibratorStateListener;
|
import android.os.IVibratorStateListener;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
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 NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
|
||||||
private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
|
private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
|
||||||
.setBatterySaverEnabled(true).build();
|
.setBatterySaverEnabled(true).build();
|
||||||
|
private static final AudioAttributes AUDIO_ATTRS =
|
||||||
|
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build();
|
||||||
private static final VibrationAttributes ALARM_ATTRS =
|
private static final VibrationAttributes ALARM_ATTRS =
|
||||||
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
|
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
|
||||||
private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
|
private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
|
||||||
@@ -136,6 +142,7 @@ public class VibratorManagerServiceTest {
|
|||||||
private TestLooper mTestLooper;
|
private TestLooper mTestLooper;
|
||||||
private FakeVibrator mVibrator;
|
private FakeVibrator mVibrator;
|
||||||
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
|
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
|
||||||
|
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@@ -208,6 +215,9 @@ public class VibratorManagerServiceTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void addService(String name, IBinder service) {
|
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));
|
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) {
|
private VibrationEffectSegment expectedPrebaked(int effectId) {
|
||||||
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
|
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user