Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c45394fd authored by Lais Andrade's avatar Lais Andrade
Browse files

Use vibrator HAL controller for prebaked effect

The new controller always triggers the callback, even if the HAL does
not support it. This requires changes in the VibratorService.java code
to rely on the native callback and use the ASYNC_TIMEOUT_MULTIPLIER for
the fallback.

This is the last step on the vibrator controller integration, and thus
removed the unused code from native VibratorService.cpp.

Bug: b/153418251
Test: atest FrameworksServicesTests:VibratorServiceTest
Change-Id: I65921dac3758a4f53612928a593af7461566a024
parent bc5ff34b
Loading
Loading
Loading
Loading
+14 −43
Original line number Original line Diff line number Diff line
@@ -114,9 +114,6 @@ public class VibratorService extends IVibratorService.Stub
    private static final VibrationAttributes DEFAULT_ATTRIBUTES =
    private static final VibrationAttributes DEFAULT_ATTRIBUTES =
            new VibrationAttributes.Builder().build();
            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
    // 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
    // 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
    // intensity level. It's important that we apply the scaling on the delta between the two so
@@ -187,8 +184,8 @@ public class VibratorService extends IVibratorService.Stub


    static native int[] vibratorGetSupportedEffects(long controllerPtr);
    static native int[] vibratorGetSupportedEffects(long controllerPtr);


    static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
    static native long vibratorPerformEffect(
            boolean withCallback);
            long controllerPtr, long effect, long strength, Vibration vibration);


    static native void vibratorPerformComposedEffect(long controllerPtr,
    static native void vibratorPerformComposedEffect(long controllerPtr,
            VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
            VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
@@ -898,19 +895,11 @@ public class VibratorService extends IVibratorService.Stub
        }
        }
    }
    }


    private final Runnable mVibrationEndRunnable = new Runnable() {
        @Override
        public void run() {
            onVibrationFinished();
        }
    };

    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private void doCancelVibrateLocked() {
    private void doCancelVibrateLocked() {
        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
        try {
        try {
            mH.removeCallbacks(mVibrationEndRunnable);
            if (mThread != null) {
            if (mThread != null) {
                mThread.cancel();
                mThread.cancel();
                mThread = null;
                mThread = null;
@@ -958,13 +947,11 @@ public class VibratorService extends IVibratorService.Stub
    private void startVibrationInnerLocked(Vibration vib) {
    private void startVibrationInnerLocked(Vibration vib) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
        try {
        try {
            long timeout = 0;
            mCurrentVibration = vib;
            mCurrentVibration = vib;
            if (vib.effect instanceof VibrationEffect.OneShot) {
            if (vib.effect instanceof VibrationEffect.OneShot) {
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
                timeout = oneShot.getDuration() * ASYNC_TIMEOUT_MULTIPLIER;
            } else if (vib.effect instanceof VibrationEffect.Waveform) {
            } else if (vib.effect instanceof VibrationEffect.Waveform) {
                // mThread better be null here. doCancelVibrate should always be
                // mThread better be null here. doCancelVibrate should always be
                // called before startNextVibrationLocked or startVibrationLocked.
                // called before startNextVibrationLocked or startVibrationLocked.
@@ -973,24 +960,13 @@ public class VibratorService extends IVibratorService.Stub
                mThread.start();
                mThread.start();
            } else if (vib.effect instanceof VibrationEffect.Prebaked) {
            } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                timeout = doVibratorPrebakedEffectLocked(vib);
                doVibratorPrebakedEffectLocked(vib);
            } else if (vib.effect instanceof  VibrationEffect.Composed) {
            } else if (vib.effect instanceof  VibrationEffect.Composed) {
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                doVibratorComposedEffectLocked(vib);
                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 {
            } else {
                Slog.e(TAG, "Unknown vibration type, ignoring");
                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 {
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
        }
@@ -1354,7 +1330,7 @@ public class VibratorService extends IVibratorService.Stub
    }
    }


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private long doVibratorPrebakedEffectLocked(Vibration vib) {
    private void doVibratorPrebakedEffectLocked(Vibration vib) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
        try {
        try {
            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
@@ -1364,25 +1340,20 @@ public class VibratorService extends IVibratorService.Stub
            }
            }
            // Input devices don't support prebaked effect, so skip trying it with them.
            // Input devices don't support prebaked effect, so skip trying it with them.
            if (!usingInputDeviceVibrators) {
            if (!usingInputDeviceVibrators) {
                long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(),
                long duration = mNativeWrapper.vibratorPerformEffect(
                        prebaked.getEffectStrength(), vib,
                        prebaked.getId(), prebaked.getEffectStrength(), vib);
                        hasCapability(IVibrator.CAP_PERFORM_CALLBACK));
                if (duration > 0) {
                long timeout = duration;
                if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
                    timeout *= ASYNC_TIMEOUT_MULTIPLIER;
                }
                if (timeout > 0) {
                    noteVibratorOnLocked(vib.uid, duration);
                    noteVibratorOnLocked(vib.uid, duration);
                    return timeout;
                    return;
                }
                }
            }
            }
            if (!prebaked.shouldFallback()) {
            if (!prebaked.shouldFallback()) {
                return 0;
                return;
            }
            }
            VibrationEffect effect = getFallbackEffect(prebaked.getId());
            VibrationEffect effect = getFallbackEffect(prebaked.getId());
            if (effect == null) {
            if (effect == null) {
                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
                return 0;
                return;
            }
            }
            Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
            Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                    vib.opPkg, vib.reason + " (fallback)");
                    vib.opPkg, vib.reason + " (fallback)");
@@ -1390,7 +1361,7 @@ public class VibratorService extends IVibratorService.Stub
            linkVibration(fallbackVib);
            linkVibration(fallbackVib);
            applyVibrationIntensityScalingLocked(fallbackVib, intensity);
            applyVibrationIntensityScalingLocked(fallbackVib, intensity);
            startVibrationInnerLocked(fallbackVib);
            startVibrationInnerLocked(fallbackVib);
            return 0;
            return;
        } finally {
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
        }
@@ -1789,9 +1760,9 @@ public class VibratorService extends IVibratorService.Stub
        }
        }


        /** Turns vibrator on to perform one of the supported effects. */
        /** Turns vibrator on to perform one of the supported effects. */
        public long vibratorPerformEffect(long effect, long strength, Vibration vibration,
        public long vibratorPerformEffect(long effect, long strength, Vibration vibration) {
                boolean withCallback) {
            return VibratorService.vibratorPerformEffect(
            return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback);
                    mNativeControllerPtr, effect, strength, vibration);
        }
        }


        /** Turns vibrator on to perform one of the supported composed effects. */
        /** Turns vibrator on to perform one of the supported composed effects. */
+14 −226
Original line number Original line Diff line number Diff line
@@ -17,9 +17,7 @@
#define LOG_TAG "VibratorService"
#define LOG_TAG "VibratorService"


#include <android/hardware/vibrator/1.3/IVibrator.h>
#include <android/hardware/vibrator/1.3/IVibrator.h>
#include <android/hardware/vibrator/BnVibratorCallback.h>
#include <android/hardware/vibrator/IVibrator.h>
#include <android/hardware/vibrator/IVibrator.h>
#include <binder/IServiceManager.h>


#include "jni.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include <nativehelper/JNIHelp.h>
@@ -28,19 +26,11 @@


#include <utils/misc.h>
#include <utils/misc.h>
#include <utils/Log.h>
#include <utils/Log.h>
#include <hardware/vibrator.h>


#include <inttypes.h>
#include <inttypes.h>
#include <stdio.h>


#include <vibratorservice/VibratorHalController.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_0 = android::hardware::vibrator::V1_0;
namespace V1_1 = android::hardware::vibrator::V1_1;
namespace V1_1 = android::hardware::vibrator::V1_1;
namespace V1_2 = android::hardware::vibrator::V1_2;
namespace V1_2 = android::hardware::vibrator::V1_2;
@@ -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_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
                static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
                static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));


class VibratorCallback {
static inline void callVibrationOnComplete(jobject vibration) {
    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) {
    if (vibration == nullptr) {
    if (vibration == nullptr) {
        return;
        return;
    }
    }
@@ -257,14 +104,6 @@ static void destroyVibratorController(void* rawVibratorController) {
}
}


static jlong vibratorInit(JNIEnv* /* env */, jclass /* clazz */) {
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::unique_ptr<vibrator::HalController> controller =
            std::make_unique<vibrator::HalController>();
            std::make_unique<vibrator::HalController>();
    controller->init();
    controller->init();
@@ -342,70 +181,19 @@ static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jl
    return effects;
    return effects;
}
}


static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong effect, jlong strength,
static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
                                   jobject vibration, jboolean withCallback) {
                                   jlong effect, jlong strength, jobject vibration) {
    if (auto hal = getHal<aidl::IVibrator>()) {
    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
        int32_t lengthMs;
    if (controller == nullptr) {
        sp<AidlVibratorCallback> effectCallback =
        ALOGE("vibratorPerformEffect failed because controller was not initialized");
                (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));
        return -1;
        return -1;
    }
    }

    aidl::Effect effectType = static_cast<aidl::Effect>(effect);
        if (status == Status::OK) {
    aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength);
            return lengthMs;
    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
        } else if (status != Status::UNSUPPORTED_OPERATION) {
    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
            // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor
    auto result = controller->performEffect(effectType, effectStrength, callback);
            // doesn't have a pre-defined waveform to perform for it, so we should just give the
    return result.isOk() ? result.value().count() : -1;
            // 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;
}
}


static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
@@ -464,7 +252,7 @@ static const JNINativeMethod method_table[] = {
        {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
        {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
        {"vibratorOff", "(J)V", (void*)vibratorOff},
        {"vibratorOff", "(J)V", (void*)vibratorOff},
        {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
        {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
        {"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
        {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J",
         (void*)vibratorPerformEffect},
         (void*)vibratorPerformEffect},
        {"vibratorPerformComposedEffect",
        {"vibratorPerformComposedEffect",
         "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
         "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
+21 −41
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
@@ -310,7 +309,7 @@ public class VibratorServiceTest {
        verify(mNativeWrapperMock).vibratorPerformEffect(
        verify(mNativeWrapperMock).vibratorPerformEffect(
                eq((long) VibrationEffect.EFFECT_CLICK),
                eq((long) VibrationEffect.EFFECT_CLICK),
                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
                any(VibratorService.Vibration.class), eq(false));
                any(VibratorService.Vibration.class));
    }
    }


    @Test
    @Test
@@ -387,21 +386,26 @@ public class VibratorServiceTest {
    }
    }


    @Test
    @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();
        VibratorService service = createService();
        Mockito.clearInvocations(mNativeWrapperMock);
        Mockito.clearInvocations(mNativeWrapperMock);


        vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
        vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));

        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();
        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
    @Test
@@ -446,30 +450,6 @@ public class VibratorServiceTest {
        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
        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
    @Test
    public void vibrate_whenBinderDies_cancelsVibration() {
    public void vibrate_whenBinderDies_cancelsVibration() {
        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -574,15 +554,15 @@ public class VibratorServiceTest {


        verify(mNativeWrapperMock).vibratorPerformEffect(
        verify(mNativeWrapperMock).vibratorPerformEffect(
                eq((long) VibrationEffect.EFFECT_CLICK),
                eq((long) VibrationEffect.EFFECT_CLICK),
                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(), anyBoolean());
                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any());
        verify(mNativeWrapperMock).vibratorPerformEffect(
        verify(mNativeWrapperMock).vibratorPerformEffect(
                eq((long) VibrationEffect.EFFECT_TICK),
                eq((long) VibrationEffect.EFFECT_TICK),
                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any(), anyBoolean());
                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any());
        verify(mNativeWrapperMock).vibratorPerformEffect(
        verify(mNativeWrapperMock).vibratorPerformEffect(
                eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK),
                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(
        verify(mNativeWrapperMock, never()).vibratorPerformEffect(
                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any(), anyBoolean());
                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any());
    }
    }


    @Test
    @Test