diff options
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 |