Loading services/core/java/com/android/server/VibratorService.java +14 −43 Original line number Diff line number Diff line Loading @@ -114,9 +114,6 @@ public class VibratorService extends IVibratorService.Stub private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); // If HAL supports callbacks set the timeout to ASYNC_TIMEOUT_MULTIPLIER * duration. private static final long ASYNC_TIMEOUT_MULTIPLIER = 2; // A mapping from the intensity adjustment to the scaling to apply, where the intensity // adjustment is defined as the delta between the default intensity level and the user selected // intensity level. It's important that we apply the scaling on the delta between the two so Loading Loading @@ -187,8 +184,8 @@ public class VibratorService extends IVibratorService.Stub static native int[] vibratorGetSupportedEffects(long controllerPtr); static native long vibratorPerformEffect(long effect, long strength, Vibration vibration, boolean withCallback); static native long vibratorPerformEffect( long controllerPtr, long effect, long strength, Vibration vibration); static native void vibratorPerformComposedEffect(long controllerPtr, VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration); Loading Loading @@ -898,19 +895,11 @@ public class VibratorService extends IVibratorService.Stub } } private final Runnable mVibrationEndRunnable = new Runnable() { @Override public void run() { onVibrationFinished(); } }; @GuardedBy("mLock") private void doCancelVibrateLocked() { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked"); try { mH.removeCallbacks(mVibrationEndRunnable); if (mThread != null) { mThread.cancel(); mThread = null; Loading Loading @@ -958,13 +947,11 @@ 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); 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. Loading @@ -973,24 +960,13 @@ public class VibratorService extends IVibratorService.Stub mThread.start(); } else if (vib.effect instanceof VibrationEffect.Prebaked) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); timeout = doVibratorPrebakedEffectLocked(vib); doVibratorPrebakedEffectLocked(vib); } else if (vib.effect instanceof VibrationEffect.Composed) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorComposedEffectLocked(vib); // FIXME: We rely on the completion callback here, but I don't think we require that // 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. 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 @@ -1354,7 +1330,7 @@ public class VibratorService extends IVibratorService.Stub } @GuardedBy("mLock") private long doVibratorPrebakedEffectLocked(Vibration vib) { private void doVibratorPrebakedEffectLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked"); try { final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; Loading @@ -1364,25 +1340,20 @@ public class VibratorService extends IVibratorService.Stub } // Input devices don't support prebaked effect, so skip trying it with them. if (!usingInputDeviceVibrators) { long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(), prebaked.getEffectStrength(), vib, hasCapability(IVibrator.CAP_PERFORM_CALLBACK)); long timeout = duration; if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { timeout *= ASYNC_TIMEOUT_MULTIPLIER; } if (timeout > 0) { long duration = mNativeWrapper.vibratorPerformEffect( prebaked.getId(), prebaked.getEffectStrength(), vib); if (duration > 0) { noteVibratorOnLocked(vib.uid, duration); return timeout; return; } } if (!prebaked.shouldFallback()) { return 0; return; } VibrationEffect effect = getFallbackEffect(prebaked.getId()); if (effect == null) { Slog.w(TAG, "Failed to play prebaked effect, no fallback"); return 0; return; } Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid, vib.opPkg, vib.reason + " (fallback)"); Loading @@ -1390,7 +1361,7 @@ public class VibratorService extends IVibratorService.Stub linkVibration(fallbackVib); applyVibrationIntensityScalingLocked(fallbackVib, intensity); startVibrationInnerLocked(fallbackVib); return 0; return; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading Loading @@ -1789,9 +1760,9 @@ public class VibratorService extends IVibratorService.Stub } /** Turns vibrator on to perform one of the supported effects. */ public long vibratorPerformEffect(long effect, long strength, Vibration vibration, boolean withCallback) { return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback); public long vibratorPerformEffect(long effect, long strength, Vibration vibration) { return VibratorService.vibratorPerformEffect( mNativeControllerPtr, effect, strength, vibration); } /** Turns vibrator on to perform one of the supported composed effects. */ Loading services/core/jni/com_android_server_VibratorService.cpp +14 −226 Original line number Diff line number Diff line Loading @@ -17,9 +17,7 @@ #define LOG_TAG "VibratorService" #include <android/hardware/vibrator/1.3/IVibrator.h> #include <android/hardware/vibrator/BnVibratorCallback.h> #include <android/hardware/vibrator/IVibrator.h> #include <binder/IServiceManager.h> #include "jni.h" #include <nativehelper/JNIHelp.h> Loading @@ -28,19 +26,11 @@ #include <utils/misc.h> #include <utils/Log.h> #include <hardware/vibrator.h> #include <inttypes.h> #include <stdio.h> #include <vibratorservice/VibratorHalController.h> using android::hardware::Return; using android::hardware::Void; using android::hardware::vibrator::V1_0::EffectStrength; using android::hardware::vibrator::V1_0::Status; using android::hardware::vibrator::V1_1::Effect_1_1; namespace V1_0 = android::hardware::vibrator::V1_0; namespace V1_1 = android::hardware::vibrator::V1_1; namespace V1_2 = android::hardware::vibrator::V1_2; Loading Loading @@ -87,150 +77,7 @@ static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_15) == static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK)); class VibratorCallback { public: VibratorCallback(JNIEnv *env, jobject vibration) : mVibration(MakeGlobalRefOrDie(env, vibration)) {} ~VibratorCallback() { JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mVibration); } void onComplete() { auto env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(mVibration, sMethodIdOnComplete); } private: jobject mVibration; }; class AidlVibratorCallback : public aidl::BnVibratorCallback { public: AidlVibratorCallback(JNIEnv *env, jobject vibration) : mCb(env, vibration) {} binder::Status onComplete() override { mCb.onComplete(); return binder::Status::ok(); // oneway, local call } private: VibratorCallback mCb; }; static constexpr int NUM_TRIES = 2; template<class R> inline R NoneStatus() { using ::android::hardware::Status; return Status::fromExceptionCode(Status::EX_NONE); } template<> inline binder::Status NoneStatus() { using binder::Status; return Status::fromExceptionCode(Status::EX_NONE); } // Creates a Return<R> with STATUS::EX_NULL_POINTER. template<class R> inline R NullptrStatus() { using ::android::hardware::Status; return Status::fromExceptionCode(Status::EX_NULL_POINTER); } template<> inline binder::Status NullptrStatus() { using binder::Status; return Status::fromExceptionCode(Status::EX_NULL_POINTER); } template <typename I> sp<I> getService() { return I::getService(); } template <> sp<aidl::IVibrator> getService() { return waitForVintfService<aidl::IVibrator>(); } template <typename I> sp<I> tryGetService() { return I::tryGetService(); } template <> sp<aidl::IVibrator> tryGetService() { return checkVintfService<aidl::IVibrator>(); } template <typename I> class HalWrapper { public: static std::unique_ptr<HalWrapper> Create() { // Assume that if getService returns a nullptr, HAL is not available on the // device. auto hal = getService<I>(); return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr; } // Helper used to transparently deal with the vibrator HAL becoming unavailable. template<class R, class... Args0, class... Args1> R call(R (I::* fn)(Args0...), Args1&&... args1) { // Return<R> doesn't have a default constructor, so make a Return<R> with // STATUS::EX_NONE. R ret{NoneStatus<R>()}; // Note that ret is guaranteed to be changed after this loop. for (int i = 0; i < NUM_TRIES; ++i) { ret = (mHal == nullptr) ? NullptrStatus<R>() : (*mHal.*fn)(std::forward<Args1>(args1)...); if (ret.isOk()) { break; } ALOGE("Failed to issue command to vibrator HAL. Retrying."); // Restoring connection to the HAL. mHal = tryGetService<I>(); } return ret; } private: HalWrapper(sp<I> &&hal) : mHal(std::move(hal)) {} private: sp<I> mHal; }; template <typename I> static auto getHal() { static auto sHalWrapper = HalWrapper<I>::Create(); return sHalWrapper.get(); } template<class R, class I, class... Args0, class... Args1> R halCall(R (I::* fn)(Args0...), Args1&&... args1) { auto hal = getHal<I>(); return hal ? hal->call(fn, std::forward<Args1>(args1)...) : NullptrStatus<R>(); } template<class R> bool isValidEffect(jlong effect) { if (effect < 0) { return false; } R val = static_cast<R>(effect); auto iter = hardware::hidl_enum_range<R>(); return val >= *iter.begin() && val <= *std::prev(iter.end()); } static void callVibrationOnComplete(jobject vibration) { static inline void callVibrationOnComplete(jobject vibration) { if (vibration == nullptr) { return; } Loading @@ -257,14 +104,6 @@ static void destroyVibratorController(void* rawVibratorController) { } static jlong vibratorInit(JNIEnv* /* env */, jclass /* clazz */) { if (auto hal = getHal<aidl::IVibrator>()) { // IBinder::pingBinder isn't accessible as a pointer function // but getCapabilities can serve the same purpose int32_t cap; hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk(); } else { halCall(&V1_0::IVibrator::ping).isOk(); } std::unique_ptr<vibrator::HalController> controller = std::make_unique<vibrator::HalController>(); controller->init(); Loading Loading @@ -342,70 +181,19 @@ static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jl return effects; } static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong effect, jlong strength, jobject vibration, jboolean withCallback) { if (auto hal = getHal<aidl::IVibrator>()) { int32_t lengthMs; sp<AidlVibratorCallback> effectCallback = (withCallback != JNI_FALSE ? new AidlVibratorCallback(env, vibration) : nullptr); aidl::Effect effectType(static_cast<aidl::Effect>(effect)); aidl::EffectStrength effectStrength(static_cast<aidl::EffectStrength>(strength)); auto status = hal->call(&aidl::IVibrator::perform, effectType, effectStrength, effectCallback, &lengthMs); if (!status.isOk()) { if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) { ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32 ": %s", static_cast<int64_t>(effect), static_cast<int32_t>(strength), status.toString8().string()); } return -1; } return lengthMs; } else { Status status; uint32_t lengthMs; auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) { status = retStatus; lengthMs = retLengthMs; }; EffectStrength effectStrength(static_cast<EffectStrength>(strength)); Return<void> ret; if (isValidEffect<V1_0::Effect>(effect)) { ret = halCall(&V1_0::IVibrator::perform, static_cast<V1_0::Effect>(effect), effectStrength, callback); } else if (isValidEffect<Effect_1_1>(effect)) { ret = halCall(&V1_1::IVibrator::perform_1_1, static_cast<Effect_1_1>(effect), effectStrength, callback); } else if (isValidEffect<V1_2::Effect>(effect)) { ret = halCall(&V1_2::IVibrator::perform_1_2, static_cast<V1_2::Effect>(effect), effectStrength, callback); } else if (isValidEffect<V1_3::Effect>(effect)) { ret = halCall(&V1_3::IVibrator::perform_1_3, static_cast<V1_3::Effect>(effect), effectStrength, callback); } else { ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")", static_cast<int32_t>(effect)); return -1; } if (!ret.isOk()) { ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect)); static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong effect, jlong strength, jobject vibration) { vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr); if (controller == nullptr) { ALOGE("vibratorPerformEffect failed because controller was not initialized"); return -1; } if (status == Status::OK) { return lengthMs; } else if (status != Status::UNSUPPORTED_OPERATION) { // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor // doesn't have a pre-defined waveform to perform for it, so we should just give the // opportunity to fall back to the framework waveforms. ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32 ", error=%" PRIu32 ").", static_cast<int64_t>(effect), static_cast<int32_t>(strength), static_cast<uint32_t>(status)); } } return -1; aidl::Effect effectType = static_cast<aidl::Effect>(effect); aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength); jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration); auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); }; auto result = controller->performEffect(effectType, effectStrength, callback); return result.isOk() ? result.value().count() : -1; } static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, Loading Loading @@ -464,7 +252,7 @@ static const JNINativeMethod method_table[] = { {"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", {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J", (void*)vibratorPerformEffect}, {"vibratorPerformComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/" Loading services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +21 −41 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -310,7 +309,7 @@ public class VibratorServiceTest { verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(VibratorService.Vibration.class), eq(false)); any(VibratorService.Vibration.class)); } @Test Loading Loading @@ -387,21 +386,26 @@ public class VibratorServiceTest { } @Test public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() { public void vibrate_withPrebakedAndNativeCallbackTriggered_finishesVibration() { when(mNativeWrapperMock.vibratorGetSupportedEffects()) .thenReturn(new int[]{VibrationEffect.EFFECT_CLICK}); doAnswer(invocation -> { ((VibratorService.Vibration) invocation.getArgument(2)).onComplete(); return 10_000L; // 10s }).when(mNativeWrapperMock).vibratorPerformEffect( anyLong(), anyLong(), any(VibratorService.Vibration.class)); 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(); vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); verify(mNativeWrapperMock).vibratorOff(); InOrder inOrderVerifier = inOrder(mNativeWrapperMock); inOrderVerifier.verify(mNativeWrapperMock).vibratorOff(); inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(VibratorService.Vibration.class)); inOrderVerifier.verify(mNativeWrapperMock).vibratorOff(); } @Test Loading Loading @@ -446,30 +450,6 @@ public class VibratorServiceTest { 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); Loading Loading @@ -574,15 +554,15 @@ public class VibratorServiceTest { verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any()); verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_TICK), eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any()); verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any()); verify(mNativeWrapperMock, never()).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any()); } @Test Loading Loading
services/core/java/com/android/server/VibratorService.java +14 −43 Original line number Diff line number Diff line Loading @@ -114,9 +114,6 @@ public class VibratorService extends IVibratorService.Stub private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); // If HAL supports callbacks set the timeout to ASYNC_TIMEOUT_MULTIPLIER * duration. private static final long ASYNC_TIMEOUT_MULTIPLIER = 2; // A mapping from the intensity adjustment to the scaling to apply, where the intensity // adjustment is defined as the delta between the default intensity level and the user selected // intensity level. It's important that we apply the scaling on the delta between the two so Loading Loading @@ -187,8 +184,8 @@ public class VibratorService extends IVibratorService.Stub static native int[] vibratorGetSupportedEffects(long controllerPtr); static native long vibratorPerformEffect(long effect, long strength, Vibration vibration, boolean withCallback); static native long vibratorPerformEffect( long controllerPtr, long effect, long strength, Vibration vibration); static native void vibratorPerformComposedEffect(long controllerPtr, VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration); Loading Loading @@ -898,19 +895,11 @@ public class VibratorService extends IVibratorService.Stub } } private final Runnable mVibrationEndRunnable = new Runnable() { @Override public void run() { onVibrationFinished(); } }; @GuardedBy("mLock") private void doCancelVibrateLocked() { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked"); try { mH.removeCallbacks(mVibrationEndRunnable); if (mThread != null) { mThread.cancel(); mThread = null; Loading Loading @@ -958,13 +947,11 @@ 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); 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. Loading @@ -973,24 +960,13 @@ public class VibratorService extends IVibratorService.Stub mThread.start(); } else if (vib.effect instanceof VibrationEffect.Prebaked) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); timeout = doVibratorPrebakedEffectLocked(vib); doVibratorPrebakedEffectLocked(vib); } else if (vib.effect instanceof VibrationEffect.Composed) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorComposedEffectLocked(vib); // FIXME: We rely on the completion callback here, but I don't think we require that // 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. 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 @@ -1354,7 +1330,7 @@ public class VibratorService extends IVibratorService.Stub } @GuardedBy("mLock") private long doVibratorPrebakedEffectLocked(Vibration vib) { private void doVibratorPrebakedEffectLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked"); try { final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; Loading @@ -1364,25 +1340,20 @@ public class VibratorService extends IVibratorService.Stub } // Input devices don't support prebaked effect, so skip trying it with them. if (!usingInputDeviceVibrators) { long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(), prebaked.getEffectStrength(), vib, hasCapability(IVibrator.CAP_PERFORM_CALLBACK)); long timeout = duration; if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { timeout *= ASYNC_TIMEOUT_MULTIPLIER; } if (timeout > 0) { long duration = mNativeWrapper.vibratorPerformEffect( prebaked.getId(), prebaked.getEffectStrength(), vib); if (duration > 0) { noteVibratorOnLocked(vib.uid, duration); return timeout; return; } } if (!prebaked.shouldFallback()) { return 0; return; } VibrationEffect effect = getFallbackEffect(prebaked.getId()); if (effect == null) { Slog.w(TAG, "Failed to play prebaked effect, no fallback"); return 0; return; } Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid, vib.opPkg, vib.reason + " (fallback)"); Loading @@ -1390,7 +1361,7 @@ public class VibratorService extends IVibratorService.Stub linkVibration(fallbackVib); applyVibrationIntensityScalingLocked(fallbackVib, intensity); startVibrationInnerLocked(fallbackVib); return 0; return; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading Loading @@ -1789,9 +1760,9 @@ public class VibratorService extends IVibratorService.Stub } /** Turns vibrator on to perform one of the supported effects. */ public long vibratorPerformEffect(long effect, long strength, Vibration vibration, boolean withCallback) { return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback); public long vibratorPerformEffect(long effect, long strength, Vibration vibration) { return VibratorService.vibratorPerformEffect( mNativeControllerPtr, effect, strength, vibration); } /** Turns vibrator on to perform one of the supported composed effects. */ Loading
services/core/jni/com_android_server_VibratorService.cpp +14 −226 Original line number Diff line number Diff line Loading @@ -17,9 +17,7 @@ #define LOG_TAG "VibratorService" #include <android/hardware/vibrator/1.3/IVibrator.h> #include <android/hardware/vibrator/BnVibratorCallback.h> #include <android/hardware/vibrator/IVibrator.h> #include <binder/IServiceManager.h> #include "jni.h" #include <nativehelper/JNIHelp.h> Loading @@ -28,19 +26,11 @@ #include <utils/misc.h> #include <utils/Log.h> #include <hardware/vibrator.h> #include <inttypes.h> #include <stdio.h> #include <vibratorservice/VibratorHalController.h> using android::hardware::Return; using android::hardware::Void; using android::hardware::vibrator::V1_0::EffectStrength; using android::hardware::vibrator::V1_0::Status; using android::hardware::vibrator::V1_1::Effect_1_1; namespace V1_0 = android::hardware::vibrator::V1_0; namespace V1_1 = android::hardware::vibrator::V1_1; namespace V1_2 = android::hardware::vibrator::V1_2; Loading Loading @@ -87,150 +77,7 @@ static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_15) == static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK)); class VibratorCallback { public: VibratorCallback(JNIEnv *env, jobject vibration) : mVibration(MakeGlobalRefOrDie(env, vibration)) {} ~VibratorCallback() { JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mVibration); } void onComplete() { auto env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(mVibration, sMethodIdOnComplete); } private: jobject mVibration; }; class AidlVibratorCallback : public aidl::BnVibratorCallback { public: AidlVibratorCallback(JNIEnv *env, jobject vibration) : mCb(env, vibration) {} binder::Status onComplete() override { mCb.onComplete(); return binder::Status::ok(); // oneway, local call } private: VibratorCallback mCb; }; static constexpr int NUM_TRIES = 2; template<class R> inline R NoneStatus() { using ::android::hardware::Status; return Status::fromExceptionCode(Status::EX_NONE); } template<> inline binder::Status NoneStatus() { using binder::Status; return Status::fromExceptionCode(Status::EX_NONE); } // Creates a Return<R> with STATUS::EX_NULL_POINTER. template<class R> inline R NullptrStatus() { using ::android::hardware::Status; return Status::fromExceptionCode(Status::EX_NULL_POINTER); } template<> inline binder::Status NullptrStatus() { using binder::Status; return Status::fromExceptionCode(Status::EX_NULL_POINTER); } template <typename I> sp<I> getService() { return I::getService(); } template <> sp<aidl::IVibrator> getService() { return waitForVintfService<aidl::IVibrator>(); } template <typename I> sp<I> tryGetService() { return I::tryGetService(); } template <> sp<aidl::IVibrator> tryGetService() { return checkVintfService<aidl::IVibrator>(); } template <typename I> class HalWrapper { public: static std::unique_ptr<HalWrapper> Create() { // Assume that if getService returns a nullptr, HAL is not available on the // device. auto hal = getService<I>(); return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr; } // Helper used to transparently deal with the vibrator HAL becoming unavailable. template<class R, class... Args0, class... Args1> R call(R (I::* fn)(Args0...), Args1&&... args1) { // Return<R> doesn't have a default constructor, so make a Return<R> with // STATUS::EX_NONE. R ret{NoneStatus<R>()}; // Note that ret is guaranteed to be changed after this loop. for (int i = 0; i < NUM_TRIES; ++i) { ret = (mHal == nullptr) ? NullptrStatus<R>() : (*mHal.*fn)(std::forward<Args1>(args1)...); if (ret.isOk()) { break; } ALOGE("Failed to issue command to vibrator HAL. Retrying."); // Restoring connection to the HAL. mHal = tryGetService<I>(); } return ret; } private: HalWrapper(sp<I> &&hal) : mHal(std::move(hal)) {} private: sp<I> mHal; }; template <typename I> static auto getHal() { static auto sHalWrapper = HalWrapper<I>::Create(); return sHalWrapper.get(); } template<class R, class I, class... Args0, class... Args1> R halCall(R (I::* fn)(Args0...), Args1&&... args1) { auto hal = getHal<I>(); return hal ? hal->call(fn, std::forward<Args1>(args1)...) : NullptrStatus<R>(); } template<class R> bool isValidEffect(jlong effect) { if (effect < 0) { return false; } R val = static_cast<R>(effect); auto iter = hardware::hidl_enum_range<R>(); return val >= *iter.begin() && val <= *std::prev(iter.end()); } static void callVibrationOnComplete(jobject vibration) { static inline void callVibrationOnComplete(jobject vibration) { if (vibration == nullptr) { return; } Loading @@ -257,14 +104,6 @@ static void destroyVibratorController(void* rawVibratorController) { } static jlong vibratorInit(JNIEnv* /* env */, jclass /* clazz */) { if (auto hal = getHal<aidl::IVibrator>()) { // IBinder::pingBinder isn't accessible as a pointer function // but getCapabilities can serve the same purpose int32_t cap; hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk(); } else { halCall(&V1_0::IVibrator::ping).isOk(); } std::unique_ptr<vibrator::HalController> controller = std::make_unique<vibrator::HalController>(); controller->init(); Loading Loading @@ -342,70 +181,19 @@ static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jl return effects; } static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong effect, jlong strength, jobject vibration, jboolean withCallback) { if (auto hal = getHal<aidl::IVibrator>()) { int32_t lengthMs; sp<AidlVibratorCallback> effectCallback = (withCallback != JNI_FALSE ? new AidlVibratorCallback(env, vibration) : nullptr); aidl::Effect effectType(static_cast<aidl::Effect>(effect)); aidl::EffectStrength effectStrength(static_cast<aidl::EffectStrength>(strength)); auto status = hal->call(&aidl::IVibrator::perform, effectType, effectStrength, effectCallback, &lengthMs); if (!status.isOk()) { if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) { ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32 ": %s", static_cast<int64_t>(effect), static_cast<int32_t>(strength), status.toString8().string()); } return -1; } return lengthMs; } else { Status status; uint32_t lengthMs; auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) { status = retStatus; lengthMs = retLengthMs; }; EffectStrength effectStrength(static_cast<EffectStrength>(strength)); Return<void> ret; if (isValidEffect<V1_0::Effect>(effect)) { ret = halCall(&V1_0::IVibrator::perform, static_cast<V1_0::Effect>(effect), effectStrength, callback); } else if (isValidEffect<Effect_1_1>(effect)) { ret = halCall(&V1_1::IVibrator::perform_1_1, static_cast<Effect_1_1>(effect), effectStrength, callback); } else if (isValidEffect<V1_2::Effect>(effect)) { ret = halCall(&V1_2::IVibrator::perform_1_2, static_cast<V1_2::Effect>(effect), effectStrength, callback); } else if (isValidEffect<V1_3::Effect>(effect)) { ret = halCall(&V1_3::IVibrator::perform_1_3, static_cast<V1_3::Effect>(effect), effectStrength, callback); } else { ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")", static_cast<int32_t>(effect)); return -1; } if (!ret.isOk()) { ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect)); static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong effect, jlong strength, jobject vibration) { vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr); if (controller == nullptr) { ALOGE("vibratorPerformEffect failed because controller was not initialized"); return -1; } if (status == Status::OK) { return lengthMs; } else if (status != Status::UNSUPPORTED_OPERATION) { // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor // doesn't have a pre-defined waveform to perform for it, so we should just give the // opportunity to fall back to the framework waveforms. ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32 ", error=%" PRIu32 ").", static_cast<int64_t>(effect), static_cast<int32_t>(strength), static_cast<uint32_t>(status)); } } return -1; aidl::Effect effectType = static_cast<aidl::Effect>(effect); aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength); jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration); auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); }; auto result = controller->performEffect(effectType, effectStrength, callback); return result.isOk() ? result.value().count() : -1; } static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, Loading Loading @@ -464,7 +252,7 @@ static const JNINativeMethod method_table[] = { {"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", {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J", (void*)vibratorPerformEffect}, {"vibratorPerformComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/" Loading
services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +21 −41 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -310,7 +309,7 @@ public class VibratorServiceTest { verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(VibratorService.Vibration.class), eq(false)); any(VibratorService.Vibration.class)); } @Test Loading Loading @@ -387,21 +386,26 @@ public class VibratorServiceTest { } @Test public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() { public void vibrate_withPrebakedAndNativeCallbackTriggered_finishesVibration() { when(mNativeWrapperMock.vibratorGetSupportedEffects()) .thenReturn(new int[]{VibrationEffect.EFFECT_CLICK}); doAnswer(invocation -> { ((VibratorService.Vibration) invocation.getArgument(2)).onComplete(); return 10_000L; // 10s }).when(mNativeWrapperMock).vibratorPerformEffect( anyLong(), anyLong(), any(VibratorService.Vibration.class)); 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(); vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); verify(mNativeWrapperMock).vibratorOff(); InOrder inOrderVerifier = inOrder(mNativeWrapperMock); inOrderVerifier.verify(mNativeWrapperMock).vibratorOff(); inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(VibratorService.Vibration.class)); inOrderVerifier.verify(mNativeWrapperMock).vibratorOff(); } @Test Loading Loading @@ -446,30 +450,6 @@ public class VibratorServiceTest { 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); Loading Loading @@ -574,15 +554,15 @@ public class VibratorServiceTest { verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any()); verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_TICK), eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any()); verify(mNativeWrapperMock).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK), eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any()); verify(mNativeWrapperMock, never()).vibratorPerformEffect( eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any(), anyBoolean()); eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any()); } @Test Loading