Loading services/core/java/com/android/server/VibratorService.java +41 −16 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; Loading @@ -248,6 +256,7 @@ public class VibratorService extends IVibratorService.Stub this.reason = reason; } @Override public void binderDied() { synchronized (mLock) { if (this == mCurrentVibration) { Loading Loading @@ -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); Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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); } } Loading Loading @@ -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; Loading Loading @@ -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. */ Loading services/core/jni/com_android_server_VibratorService.cpp +15 −15 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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", Loading services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +108 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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)); } Loading @@ -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()); } Loading Loading @@ -349,71 +352,152 @@ public class VibratorServiceTest { 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); Loading @@ -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 Loading Loading
services/core/java/com/android/server/VibratorService.java +41 −16 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; Loading @@ -248,6 +256,7 @@ public class VibratorService extends IVibratorService.Stub this.reason = reason; } @Override public void binderDied() { synchronized (mLock) { if (this == mCurrentVibration) { Loading Loading @@ -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); Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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); } } Loading Loading @@ -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; Loading Loading @@ -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. */ Loading
services/core/jni/com_android_server_VibratorService.cpp +15 −15 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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", Loading
services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +108 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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)); } Loading @@ -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()); } Loading Loading @@ -349,71 +352,152 @@ public class VibratorServiceTest { 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); Loading @@ -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 Loading