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

Commit 590d9f30 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use vibrator HAL controller for prebaked effect"

parents 73b0bbd1 c45394fd
Loading
Loading
Loading
Loading
+14 −43
Original line number Diff line number Diff line
@@ -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
@@ -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);
@@ -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;
@@ -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.
@@ -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);
        }
@@ -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;
@@ -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)");
@@ -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);
        }
@@ -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. */
+14 −226
Original line number Diff line number Diff line
@@ -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>
@@ -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;
@@ -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;
    }
@@ -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();
@@ -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,
@@ -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/"
+21 −41
Original line number 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.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;
@@ -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
@@ -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
@@ -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);
@@ -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