diff options
| author | 2020-07-29 18:33:46 +0000 | |
|---|---|---|
| committer | 2020-08-07 14:31:06 +0000 | |
| commit | bc5ff34bad0dae8306304816f66977a125a7657c (patch) | |
| tree | 3a0dd59c8769f2b77530768f278a177604cbcfcb | |
| parent | 39502ee981c73bd16cbd6e613ff0b5019a655737 (diff) | |
Use vibrator HAL controller for vibrator on and waveform
The new controller always triggers the callback, even if the HAL does
not support it. This requires changes in the VibratorService.java code
to rely on the native callback and use the ASYNC_TIMEOUT_MULTIPLIER for
the fallback.
This also changes the Vibration class to ignore native callbacks on
waveform effects, which are controlled by a separate VibrateThread that
triggers multiple vibrator ON calls (possibly indefinitely).
Bug: b/153418251
Test: atest FrameworksServicesTests:VibratorServiceTest
Change-Id: I09c9a0fd3f839c8f9eda65d96584a3e0294c6c21
3 files changed, 164 insertions, 53 deletions
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 2fef82d352dd..80046e78858d 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -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. */ diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index f0618c23b025..b6633ced771b 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -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", diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java index 1b6ac3c84210..7d6d90c4578c 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java @@ -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(); - 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 vibrate_whenBinderDies_cancelsVibration() { + 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); 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 |