Merge "Use vibrator HAL controller for vibrator on and waveform"

This commit is contained in:
Lais Andrade
2020-08-07 17:30:20 +00:00
committed by Android (Google) Code Review
3 changed files with 165 additions and 54 deletions

View File

@@ -167,7 +167,7 @@ public class VibratorService extends IVibratorService.Stub
private boolean mIsVibrating;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
new RemoteCallbackList<>();
new RemoteCallbackList<>();
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
@@ -176,16 +176,25 @@ public class VibratorService extends IVibratorService.Stub
static native long vibratorInit();
static native long vibratorGetFinalizer();
static native boolean vibratorExists(long controllerPtr);
static native void vibratorOn(long milliseconds);
static native void vibratorOn(long controllerPtr, long milliseconds, Vibration vibration);
static native void vibratorOff(long controllerPtr);
static native void vibratorSetAmplitude(long controllerPtr, int amplitude);
static native int[] vibratorGetSupportedEffects(long controllerPtr);
static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
boolean withCallback);
static native void vibratorPerformComposedEffect(long controllerPtr,
VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
static native void vibratorSetExternalControl(long controllerPtr, boolean enabled);
static native long vibratorGetCapabilities(long controllerPtr);
static native void vibratorAlwaysOnEnable(long controllerPtr, long id, long effect,
long strength);
@@ -231,8 +240,7 @@ public class VibratorService extends IVibratorService.Stub
// The actual effect to be played.
public VibrationEffect effect;
// The original effect that was requested. This is non-null only when the original effect
// differs from the effect that's being played. Typically these two things differ because
// The original effect that was requested. Typically these two things differ because
// the effect was scaled based on the users vibration intensity settings.
public VibrationEffect originalEffect;
@@ -248,6 +256,7 @@ public class VibratorService extends IVibratorService.Stub
this.reason = reason;
}
@Override
public void binderDied() {
synchronized (mLock) {
if (this == mCurrentVibration) {
@@ -949,25 +958,22 @@ public class VibratorService extends IVibratorService.Stub
private void startVibrationInnerLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
try {
long timeout = 0;
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
timeout = oneShot.getDuration() * ASYNC_TIMEOUT_MULTIPLIER;
} else if (vib.effect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
mThread.start();
} else if (vib.effect instanceof VibrationEffect.Prebaked) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
long timeout = doVibratorPrebakedEffectLocked(vib);
if (timeout > 0) {
mH.postDelayed(mVibrationEndRunnable, timeout);
}
timeout = doVibratorPrebakedEffectLocked(vib);
} else if (vib.effect instanceof VibrationEffect.Composed) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorComposedEffectLocked(vib);
@@ -975,10 +981,16 @@ public class VibratorService extends IVibratorService.Stub
// devices which support composition also support the completion callback. If we
// ever get a device that supports the former but not the latter, then we have no
// real way of knowing how long a given effect should last.
mH.postDelayed(mVibrationEndRunnable, 10000);
timeout = 10_000;
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
}
// Post extra runnable to ensure vibration will end even if the HAL or native controller
// never triggers the callback.
// TODO: Move ASYNC_TIMEOUT_MULTIPLIER here once native controller is fully integrated.
if (timeout > 0) {
mH.postDelayed(mVibrationEndRunnable, timeout);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1271,7 +1283,18 @@ public class VibratorService extends IVibratorService.Stub
return mNativeWrapper.vibratorExists();
}
/** Vibrates with native callback trigger for {@link Vibration#onComplete()}. */
private void doVibratorOn(long millis, int amplitude, Vibration vib) {
doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib);
}
/** Vibrates without native callback. */
private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
doVibratorOn(millis, amplitude, uid, attrs, /* vib= */ null);
}
private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
@Nullable Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
@@ -1286,13 +1309,14 @@ public class VibratorService extends IVibratorService.Stub
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attrs.getAudioAttributes());
Vibrator inputDeviceVibrator = mInputDeviceVibrators.get(i);
inputDeviceVibrator.vibrate(millis, attrs.getAudioAttributes());
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
mNativeWrapper.vibratorOn(millis);
mNativeWrapper.vibratorOn(millis, vib);
doVibratorSetAmplitude(amplitude);
}
}
@@ -1576,6 +1600,7 @@ public class VibratorService extends IVibratorService.Stub
proto.flush();
}
/** Thread that plays a single {@link VibrationEffect.Waveform}. */
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
@@ -1744,8 +1769,8 @@ public class VibratorService extends IVibratorService.Stub
}
/** Turns vibrator on for given time. */
public void vibratorOn(long milliseconds) {
VibratorService.vibratorOn(milliseconds);
public void vibratorOn(long milliseconds, @Nullable Vibration vibration) {
VibratorService.vibratorOn(mNativeControllerPtr, milliseconds, vibration);
}
/** Turns vibrator off. */

View File

@@ -231,6 +231,9 @@ bool isValidEffect(jlong effect) {
}
static void callVibrationOnComplete(jobject vibration) {
if (vibration == nullptr) {
return;
}
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
jniEnv->CallVoidMethod(vibration, sMethodIdOnComplete);
jniEnv->DeleteGlobalRef(vibration);
@@ -281,18 +284,16 @@ static jboolean vibratorExists(JNIEnv* env, jclass /* clazz */, jlong controller
return controller->ping().isOk() ? JNI_TRUE : JNI_FALSE;
}
static void vibratorOn(JNIEnv* /* env */, jclass /* clazz */, jlong timeout_ms) {
if (auto hal = getHal<aidl::IVibrator>()) {
auto status = hal->call(&aidl::IVibrator::on, timeout_ms, nullptr);
if (!status.isOk()) {
ALOGE("vibratorOn command failed: %s", status.toString8().string());
}
} else {
Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
if (retStatus != Status::OK) {
ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong timeoutMs,
jobject vibration) {
vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
if (controller == nullptr) {
ALOGE("vibratorOn failed because controller was not initialized");
return;
}
jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
controller->on(std::chrono::milliseconds(timeoutMs), callback);
}
static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
@@ -420,9 +421,8 @@ static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong
jobject element = env->GetObjectArrayElement(composition, i);
effects.push_back(effectFromJavaPrimitive(env, element));
}
auto callback = [vibrationRef(MakeGlobalRefOrDie(env, vibration))]() {
callVibrationOnComplete(vibrationRef);
};
jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
controller->performComposedEffect(effects, callback);
}
@@ -461,7 +461,7 @@ static const JNINativeMethod method_table[] = {
{"vibratorInit", "()J", (void*)vibratorInit},
{"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
{"vibratorExists", "(J)Z", (void*)vibratorExists},
{"vibratorOn", "(J)V", (void*)vibratorOn},
{"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
{"vibratorOff", "(J)V", (void*)vibratorOff},
{"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
{"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",

View File

@@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -65,6 +67,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
@@ -277,7 +280,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
verify(mNativeWrapperMock).vibratorOn(eq(100L));
verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(128));
}
@@ -290,7 +293,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
verify(mNativeWrapperMock).vibratorOn(eq(100L));
verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock, never()).vibratorSetAmplitude(anyInt());
}
@@ -344,76 +347,157 @@ public class VibratorServiceTest {
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createWaveform(
new long[] { 10, 10, 10 }, new int[] { 100, 200, 50 }, -1);
new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
vibrate(service, effect);
verify(mNativeWrapperMock).vibratorOff();
// Wait for VibrateThread to turn vibrator ON with total timing and no callback.
Thread.sleep(5);
verify(mNativeWrapperMock).vibratorOn(eq(30L));
verify(mNativeWrapperMock).vibratorOn(eq(30L), isNull());
// First amplitude set right away.
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
// Second amplitude set after first timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(200));
// Third amplitude set after second timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(50));
}
@Test
public void vibrate_withCallback_finishesVibrationWhenCallbackTriggered() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
return null;
}).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L),
any(VibratorService.Vibration.class));
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
verify(mNativeWrapperMock).vibratorOff();
verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
Mockito.clearInvocations(mNativeWrapperMock);
// Run the scheduled callback to finish one-shot vibration.
mTestLooper.moveTimeForward(200);
mTestLooper.dispatchAll();
verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void vibrate_withWaveformAndNativeCallback_callbackCannotBeTriggeredByNative()
throws Exception {
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{1, 3, 1, 2}, -1);
vibrate(service, effect);
// Wait for VibrateThread to finish: 1ms OFF, 3ms ON, 1ms OFF, 2ms ON.
Thread.sleep(15);
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), isNull());
inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), isNull());
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void vibrate_withComposedAndNativeCallbackTriggered_finishesVibration() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
// Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
// Vibration canceled once before perform and once by native callback.
verify(mNativeWrapperMock, times(2)).vibratorOff();
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void vibrate_withComposedAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
verify(mNativeWrapperMock).vibratorOff();
verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
Mockito.clearInvocations(mNativeWrapperMock);
// Run the scheduled callback to finish one-shot vibration.
mTestLooper.moveTimeForward(10000); // 10s
mTestLooper.dispatchAll();
verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void vibrate_whenBinderDies_cancelsVibration() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).binderDied();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
// Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
// Vibration canceled once before perform and once by native binder death.
verify(mNativeWrapperMock, times(2)).vibratorOff();
verify(mNativeWrapperMock).vibratorPerformComposedEffect(
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void cancelVibrate_withDeviceVibrating_callsVibratorOff() {
VibratorService service = createService();
vibrate(service, VibrationEffect.createOneShot(100, 128));
vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
assertTrue(service.isVibrating());
Mockito.clearInvocations(mNativeWrapperMock);
@@ -434,18 +518,20 @@ public class VibratorServiceTest {
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
return null;
}).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
Mockito.clearInvocations(mVibratorStateListenerMock);
vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE));
verify(mVibratorStateListenerMock).onVibrating(true);
// Run the scheduled callback to finish one-shot vibration.
mTestLooper.moveTimeForward(10);
mTestLooper.dispatchAll();
verify(mVibratorStateListenerMock, times(2)).onVibrating(false);
InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
}
@Test