Create vibrator controller for Vibrator HALs
Create vibrator::HalController, a vibrator::HalWrapper that manages
the connection to the underlying Vibrator HAL service available and is
responsible for reconnecting after any api call fails.
This controller also has a single vibrator::CallbackScheduler instance
that outlives the connection to the HAL and is responsible for
triggering the callbacks when the HAL does not support them. It makes
sure the callbacks will not be affected during reconnection with the HAL
service.
Bug: 153418251
Test: atest libvibratorservice_test
Change-Id: Ic6d9671468ee18d0aff4e8a80b501c7bb110477d
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index e3c254d..c45a1a1 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -17,6 +17,7 @@
srcs: [
"VibratorCallbackScheduler.cpp",
+ "VibratorHalController.cpp",
"VibratorHalWrapper.cpp",
],
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
new file mode 100644
index 0000000..ef1d061
--- /dev/null
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorHalController"
+
+#include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/hardware/vibrator/IVibrator.h>
+#include <binder/IServiceManager.h>
+#include <hardware/vibrator.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorHalWrapper.h>
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+
+using std::chrono::milliseconds;
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
+namespace Aidl = android::hardware::vibrator;
+
+namespace android {
+
+namespace vibrator {
+
+// -------------------------------------------------------------------------------------------------
+
+template <typename T>
+using hal_connect_fn = std::function<sp<T>()>;
+
+template <typename T>
+sp<T> connectToHal(bool* halExists, const hal_connect_fn<T>& connectFn, const char* halName) {
+ if (!*halExists) {
+ return nullptr;
+ }
+ sp<T> hal = connectFn();
+ if (hal) {
+ ALOGV("Successfully connected to Vibrator HAL %s service.", halName);
+ } else {
+ ALOGV("Vibrator HAL %s service not available.", halName);
+ *halExists = false;
+ }
+ return hal;
+}
+
+sp<Aidl::IVibrator> connectToAidl() {
+ static bool gHalExists = true;
+ static hal_connect_fn<Aidl::IVibrator> connectFn = []() {
+ return waitForVintfService<Aidl::IVibrator>();
+ };
+ return connectToHal(&gHalExists, connectFn, "AIDL");
+}
+
+sp<V1_0::IVibrator> connectToHidl() {
+ static bool gHalExists = true;
+ static hal_connect_fn<V1_0::IVibrator> connectFn = []() {
+ return V1_0::IVibrator::getService();
+ };
+ return connectToHal(&gHalExists, connectFn, "v1.0");
+}
+
+// -------------------------------------------------------------------------------------------------
+
+std::shared_ptr<HalWrapper> HalConnector::connect(std::shared_ptr<CallbackScheduler> scheduler) {
+ sp<Aidl::IVibrator> aidlHal = connectToAidl();
+ if (aidlHal) {
+ return std::make_shared<AidlHalWrapper>(std::move(scheduler), aidlHal);
+ }
+ sp<V1_0::IVibrator> halV1_0 = connectToHidl();
+ if (halV1_0 == nullptr) {
+ // No Vibrator HAL service available.
+ return nullptr;
+ }
+ sp<V1_3::IVibrator> halV1_3 = V1_3::IVibrator::castFrom(halV1_0);
+ if (halV1_3) {
+ ALOGV("Successfully converted to Vibrator HAL v1.3 service.");
+ return std::make_shared<HidlHalWrapperV1_3>(std::move(scheduler), halV1_3);
+ }
+ sp<V1_2::IVibrator> halV1_2 = V1_2::IVibrator::castFrom(halV1_0);
+ if (halV1_2) {
+ ALOGV("Successfully converted to Vibrator HAL v1.2 service.");
+ return std::make_shared<HidlHalWrapperV1_2>(std::move(scheduler), halV1_2);
+ }
+ sp<V1_1::IVibrator> halV1_1 = V1_1::IVibrator::castFrom(halV1_0);
+ if (halV1_1) {
+ ALOGV("Successfully converted to Vibrator HAL v1.1 service.");
+ return std::make_shared<HidlHalWrapperV1_1>(std::move(scheduler), halV1_1);
+ }
+ return std::make_shared<HidlHalWrapperV1_0>(std::move(scheduler), halV1_0);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+template <typename T>
+HalResult<T> HalController::processHalResult(HalResult<T> result, const char* functionName) {
+ if (result.isFailed()) {
+ ALOGE("%s failed: Vibrator HAL not available", functionName);
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ // Drop HAL handle. This will force future api calls to reconnect.
+ mConnectedHal = nullptr;
+ }
+ return result;
+}
+
+template <typename T>
+HalResult<T> HalController::apply(HalController::hal_fn<T>& halFn, const char* functionName) {
+ std::shared_ptr<HalWrapper> hal = nullptr;
+ {
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ if (mConnectedHal == nullptr) {
+ mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+ }
+ hal = mConnectedHal;
+ }
+ if (hal) {
+ return processHalResult(halFn(hal), functionName);
+ }
+
+ ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
+ return HalResult<T>::unsupported();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HalController::ping() {
+ hal_fn<void> pingFn = [](std::shared_ptr<HalWrapper> hal) { return hal->ping(); };
+ return apply(pingFn, "ping");
+}
+
+HalResult<void> HalController::on(milliseconds timeout,
+ const std::function<void()>& completionCallback) {
+ hal_fn<void> onFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->on(timeout, completionCallback);
+ };
+ return apply(onFn, "on");
+}
+
+HalResult<void> HalController::off() {
+ hal_fn<void> offFn = [](std::shared_ptr<HalWrapper> hal) { return hal->off(); };
+ return apply(offFn, "off");
+}
+
+HalResult<void> HalController::setAmplitude(int32_t amplitude) {
+ hal_fn<void> setAmplitudeFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->setAmplitude(amplitude);
+ };
+ return apply(setAmplitudeFn, "setAmplitude");
+}
+
+HalResult<void> HalController::setExternalControl(bool enabled) {
+ hal_fn<void> setExternalControlFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->setExternalControl(enabled);
+ };
+ return apply(setExternalControlFn, "setExternalControl");
+}
+
+HalResult<void> HalController::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
+ hal_fn<void> alwaysOnEnableFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->alwaysOnEnable(id, effect, strength);
+ };
+ return apply(alwaysOnEnableFn, "alwaysOnEnable");
+}
+
+HalResult<void> HalController::alwaysOnDisable(int32_t id) {
+ hal_fn<void> alwaysOnDisableFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->alwaysOnDisable(id);
+ };
+ return apply(alwaysOnDisableFn, "alwaysOnDisable");
+}
+
+HalResult<Capabilities> HalController::getCapabilities() {
+ hal_fn<Capabilities> getCapabilitiesFn = [](std::shared_ptr<HalWrapper> hal) {
+ return hal->getCapabilities();
+ };
+ return apply(getCapabilitiesFn, "getCapabilities");
+}
+
+HalResult<std::vector<Effect>> HalController::getSupportedEffects() {
+ hal_fn<std::vector<Effect>> getSupportedEffectsFn = [](std::shared_ptr<HalWrapper> hal) {
+ return hal->getSupportedEffects();
+ };
+ return apply(getSupportedEffectsFn, "getSupportedEffects");
+}
+
+HalResult<milliseconds> HalController::performEffect(
+ Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+ hal_fn<milliseconds> performEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->performEffect(effect, strength, completionCallback);
+ };
+ return apply(performEffectFn, "performEffect");
+}
+
+HalResult<void> HalController::performComposedEffect(
+ const std::vector<CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) {
+ hal_fn<void> performComposedEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
+ return hal->performComposedEffect(primitiveEffects, completionCallback);
+ };
+ return apply(performComposedEffectFn, "performComposedEffect");
+}
+
+}; // namespace vibrator
+
+}; // namespace android
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 1420bf5..94db538 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -68,21 +68,6 @@
// -------------------------------------------------------------------------------------------------
template <typename T>
-HalResult<T> HalResult<T>::ok(T value) {
- return HalResult(value);
-}
-
-template <typename T>
-HalResult<T> HalResult<T>::failed() {
- return HalResult(/* unsupported= */ false);
-}
-
-template <typename T>
-HalResult<T> HalResult<T>::unsupported() {
- return HalResult(/* unsupported= */ true);
-}
-
-template <typename T>
HalResult<T> HalResult<T>::fromStatus(binder::Status status, T data) {
if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
return HalResult<T>::unsupported();
@@ -119,18 +104,6 @@
// -------------------------------------------------------------------------------------------------
-HalResult<void> HalResult<void>::ok() {
- return HalResult();
-}
-
-HalResult<void> HalResult<void>::failed() {
- return HalResult(/* failed= */ true);
-}
-
-HalResult<void> HalResult<void>::unsupported() {
- return HalResult(/* failed= */ false, /* unsupported= */ true);
-}
-
HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
return HalResult<void>::unsupported();
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
new file mode 100644
index 0000000..e254969
--- /dev/null
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_OS_VIBRATORHALCONTROLLER_H
+#define ANDROID_OS_VIBRATORHALCONTROLLER_H
+
+#include <android-base/thread_annotations.h>
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+#include <vibratorservice/VibratorHalWrapper.h>
+
+namespace android {
+
+namespace vibrator {
+
+// Handles the connection to he underlying HAL implementation available.
+class HalConnector {
+public:
+ HalConnector() = default;
+ virtual ~HalConnector() = default;
+
+ virtual std::shared_ptr<HalWrapper> connect(std::shared_ptr<CallbackScheduler> scheduler);
+};
+
+// Controller for Vibrator HAL handle.
+// This relies on HalConnector to connect to the underlying Vibrator HAL service and reconnects to
+// it after each failed api call. This also ensures connecting to the service is thread-safe.
+class HalController : public HalWrapper {
+public:
+ HalController()
+ : HalController(std::make_unique<HalConnector>(), std::make_shared<CallbackScheduler>()) {
+ }
+ HalController(std::unique_ptr<HalConnector> halConnector,
+ std::shared_ptr<CallbackScheduler> callbackScheduler)
+ : HalWrapper(std::move(callbackScheduler)),
+ mHalConnector(std::move(halConnector)),
+ mConnectedHal(nullptr) {}
+ virtual ~HalController() = default;
+
+ HalResult<void> ping() final override;
+
+ HalResult<void> on(std::chrono::milliseconds timeout,
+ const std::function<void()>& completionCallback) final override;
+ HalResult<void> off() final override;
+
+ HalResult<void> setAmplitude(int32_t amplitude) final override;
+ HalResult<void> setExternalControl(bool enabled) final override;
+
+ HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+ hardware::vibrator::EffectStrength strength) final override;
+ HalResult<void> alwaysOnDisable(int32_t id) final override;
+
+ HalResult<Capabilities> getCapabilities() final override;
+ HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() final override;
+
+ HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) final override;
+
+ HalResult<void> performComposedEffect(
+ const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) final override;
+
+private:
+ std::unique_ptr<HalConnector> mHalConnector;
+ std::mutex mConnectedHalMutex;
+ // Shared pointer to allow local copies to be used by different threads.
+ std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
+
+ template <typename T>
+ HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
+
+ template <typename T>
+ using hal_fn = std::function<HalResult<T>(std::shared_ptr<HalWrapper>)>;
+
+ template <typename T>
+ HalResult<T> apply(hal_fn<T>& halFn, const char* functionName);
+};
+
+}; // namespace vibrator
+
+}; // namespace android
+
+#endif // ANDROID_OS_VIBRATORHALCONTROLLER_H
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 0f9aacb..5e3c275 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -33,9 +33,9 @@
template <typename T>
class HalResult {
public:
- static HalResult<T> ok(T value);
- static HalResult<T> failed();
- static HalResult<T> unsupported();
+ static HalResult<T> ok(T value) { return HalResult(value); }
+ static HalResult<T> failed() { return HalResult(/* unsupported= */ false); }
+ static HalResult<T> unsupported() { return HalResult(/* unsupported= */ true); }
static HalResult<T> fromStatus(binder::Status status, T data);
static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data);
@@ -65,9 +65,11 @@
template <>
class HalResult<void> {
public:
- static HalResult<void> ok();
- static HalResult<void> failed();
- static HalResult<void> unsupported();
+ static HalResult<void> ok() { return HalResult(); }
+ static HalResult<void> failed() { return HalResult(/* failed= */ true); }
+ static HalResult<void> unsupported() {
+ return HalResult(/* failed= */ false, /* unsupported= */ true);
+ }
static HalResult<void> fromStatus(binder::Status status);
static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status);
@@ -262,9 +264,8 @@
class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
public:
HidlHalWrapperV1_1(std::shared_ptr<CallbackScheduler> scheduler,
- sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
- : HidlHalWrapperV1_0(std::move(scheduler), handleV1_0),
- mHandleV1_1(hardware::vibrator::V1_1::IVibrator::castFrom(handleV1_0)) {}
+ sp<hardware::vibrator::V1_1::IVibrator> handle)
+ : HidlHalWrapperV1_0(std::move(scheduler), handle), mHandleV1_1(handle) {}
virtual ~HidlHalWrapperV1_1() = default;
virtual HalResult<std::chrono::milliseconds> performEffect(
@@ -283,9 +284,8 @@
class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
public:
HidlHalWrapperV1_2(std::shared_ptr<CallbackScheduler> scheduler,
- sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
- : HidlHalWrapperV1_1(std::move(scheduler), handleV1_0),
- mHandleV1_2(hardware::vibrator::V1_2::IVibrator::castFrom(handleV1_0)) {}
+ sp<hardware::vibrator::V1_2::IVibrator> handle)
+ : HidlHalWrapperV1_1(std::move(scheduler), handle), mHandleV1_2(handle) {}
virtual ~HidlHalWrapperV1_2() = default;
virtual HalResult<std::chrono::milliseconds> performEffect(
@@ -304,9 +304,8 @@
class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
public:
HidlHalWrapperV1_3(std::shared_ptr<CallbackScheduler> scheduler,
- sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
- : HidlHalWrapperV1_2(std::move(scheduler), handleV1_0),
- mHandleV1_3(hardware::vibrator::V1_3::IVibrator::castFrom(handleV1_0)) {}
+ sp<hardware::vibrator::V1_3::IVibrator> handle)
+ : HidlHalWrapperV1_2(std::move(scheduler), handle), mHandleV1_3(handle) {}
virtual ~HidlHalWrapperV1_3() = default;
virtual HalResult<void> setExternalControl(bool enabled) override;
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index fa399ad..9033124 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -17,6 +17,7 @@
test_suites: ["device-tests"],
srcs: [
"VibratorCallbackSchedulerTest.cpp",
+ "VibratorHalControllerTest.cpp",
"VibratorHalWrapperAidlTest.cpp",
"VibratorHalWrapperHidlV1_0Test.cpp",
"VibratorHalWrapperHidlV1_1Test.cpp",
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
new file mode 100644
index 0000000..3d8b069
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorHalControllerTest"
+
+#include <android/hardware/vibrator/IVibrator.h>
+#include <cutils/atomic.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <thread>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::CompositePrimitive;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+
+using std::chrono::milliseconds;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockHalWrapper : public vibrator::HalWrapper {
+public:
+ MockHalWrapper(std::shared_ptr<vibrator::CallbackScheduler> scheduler)
+ : HalWrapper(scheduler) {}
+ virtual ~MockHalWrapper() = default;
+
+ MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, on,
+ (milliseconds timeout, const std::function<void()>& completionCallback),
+ (override));
+ MOCK_METHOD(vibrator::HalResult<void>, off, (), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, setAmplitude, (int32_t amplitude), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, setExternalControl, (bool enabled), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, alwaysOnEnable,
+ (int32_t id, Effect effect, EffectStrength strength), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, alwaysOnDisable, (int32_t id), (override));
+ MOCK_METHOD(vibrator::HalResult<vibrator::Capabilities>, getCapabilities, (), (override));
+ MOCK_METHOD(vibrator::HalResult<std::vector<Effect>>, getSupportedEffects, (), (override));
+ MOCK_METHOD(vibrator::HalResult<milliseconds>, performEffect,
+ (Effect effect, EffectStrength strength,
+ const std::function<void()>& completionCallback),
+ (override));
+ MOCK_METHOD(vibrator::HalResult<void>, performComposedEffect,
+ (const std::vector<CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback),
+ (override));
+
+ vibrator::CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
+};
+
+class TestHalConnector : public vibrator::HalConnector {
+public:
+ TestHalConnector(int32_t* connectCounter, std::shared_ptr<MockHalWrapper> mockHal)
+ : mConnectCounter(connectCounter), mMockHal(std::move(mockHal)) {}
+ ~TestHalConnector() = default;
+
+ std::shared_ptr<vibrator::HalWrapper> connect(
+ std::shared_ptr<vibrator::CallbackScheduler>) override final {
+ android_atomic_inc(mConnectCounter);
+ return mMockHal;
+ }
+
+private:
+ int32_t* mConnectCounter;
+ std::shared_ptr<MockHalWrapper> mMockHal;
+};
+
+class FailingHalConnector : public vibrator::HalConnector {
+public:
+ FailingHalConnector(int32_t* connectCounter) : mConnectCounter(connectCounter) {}
+ ~FailingHalConnector() = default;
+
+ std::shared_ptr<vibrator::HalWrapper> connect(
+ std::shared_ptr<vibrator::CallbackScheduler>) override final {
+ android_atomic_inc(mConnectCounter);
+ return nullptr;
+ }
+
+private:
+ int32_t* mConnectCounter;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalControllerTest : public Test {
+public:
+ void SetUp() override {
+ mConnectCounter = 0;
+ auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
+ mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
+ auto halConnector = std::make_unique<TestHalConnector>(&mConnectCounter, mMockHal);
+ mController = std::make_unique<vibrator::HalController>(std::move(halConnector),
+ std::move(callbackScheduler));
+ ASSERT_NE(mController, nullptr);
+ }
+
+protected:
+ int32_t mConnectCounter;
+ std::shared_ptr<MockHalWrapper> mMockHal;
+ std::unique_ptr<vibrator::HalController> mController;
+
+ void setHalExpectations(std::vector<CompositeEffect> compositeEffects,
+ vibrator::HalResult<void> voidResult,
+ vibrator::HalResult<vibrator::Capabilities> capabilitiesResult,
+ vibrator::HalResult<std::vector<Effect>> effectsResult,
+ vibrator::HalResult<milliseconds> durationResult) {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), ping()).Times(Exactly(1)).WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), on(Eq(10ms), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), off()).Times(Exactly(1)).WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(255)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(),
+ alwaysOnEnable(Eq(1), Eq(Effect::CLICK), Eq(EffectStrength::LIGHT)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), getCapabilities())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(capabilitiesResult));
+ EXPECT_CALL(*mMockHal.get(), getSupportedEffects())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(effectsResult));
+ EXPECT_CALL(*mMockHal.get(), performEffect(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(durationResult));
+ EXPECT_CALL(*mMockHal.get(), performComposedEffect(Eq(compositeEffects), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(voidResult));
+ }
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorHalControllerTest, TestApiCallsAreForwardedToHal) {
+ std::vector<Effect> supportedEffects;
+ supportedEffects.push_back(Effect::CLICK);
+ supportedEffects.push_back(Effect::TICK);
+ std::vector<CompositeEffect> compositeEffects;
+ compositeEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::SPIN, 100ms, 0.5f));
+ compositeEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::THUD, 1000ms, 1.0f));
+
+ setHalExpectations(compositeEffects, vibrator::HalResult<void>::ok(),
+ vibrator::HalResult<vibrator::Capabilities>::ok(
+ vibrator::Capabilities::ON_CALLBACK),
+ vibrator::HalResult<std::vector<Effect>>::ok(supportedEffects),
+ vibrator::HalResult<milliseconds>::ok(100ms));
+
+ ASSERT_TRUE(mController->ping().isOk());
+ ASSERT_TRUE(mController->on(10ms, []() {}).isOk());
+ ASSERT_TRUE(mController->off().isOk());
+ ASSERT_TRUE(mController->setAmplitude(255).isOk());
+ ASSERT_TRUE(mController->setExternalControl(true).isOk());
+ ASSERT_TRUE(mController->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT).isOk());
+ ASSERT_TRUE(mController->alwaysOnDisable(1).isOk());
+
+ auto getCapabilitiesResult = mController->getCapabilities();
+ ASSERT_TRUE(getCapabilitiesResult.isOk());
+ ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, getCapabilitiesResult.value());
+
+ auto getSupportedEffectsResult = mController->getSupportedEffects();
+ ASSERT_TRUE(getSupportedEffectsResult.isOk());
+ ASSERT_EQ(supportedEffects, getSupportedEffectsResult.value());
+
+ auto performEffectResult =
+ mController->performEffect(Effect::CLICK, EffectStrength::LIGHT, []() {});
+ ASSERT_TRUE(performEffectResult.isOk());
+ ASSERT_EQ(100ms, performEffectResult.value());
+
+ ASSERT_TRUE(mController->performComposedEffect(compositeEffects, []() {}).isOk());
+
+ // No reconnection attempt was made after the first one.
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
+ setHalExpectations(std::vector<CompositeEffect>(), vibrator::HalResult<void>::unsupported(),
+ vibrator::HalResult<vibrator::Capabilities>::unsupported(),
+ vibrator::HalResult<std::vector<Effect>>::unsupported(),
+ vibrator::HalResult<milliseconds>::unsupported());
+
+ ASSERT_EQ(0, mConnectCounter);
+
+ ASSERT_TRUE(mController->ping().isUnsupported());
+ ASSERT_TRUE(mController->on(10ms, []() {}).isUnsupported());
+ ASSERT_TRUE(mController->off().isUnsupported());
+ ASSERT_TRUE(mController->setAmplitude(255).isUnsupported());
+ ASSERT_TRUE(mController->setExternalControl(true).isUnsupported());
+ ASSERT_TRUE(
+ mController->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT).isUnsupported());
+ ASSERT_TRUE(mController->alwaysOnDisable(1).isUnsupported());
+ ASSERT_TRUE(mController->getCapabilities().isUnsupported());
+ ASSERT_TRUE(mController->getSupportedEffects().isUnsupported());
+ ASSERT_TRUE(mController->performEffect(Effect::CLICK, EffectStrength::LIGHT, []() {})
+ .isUnsupported());
+ ASSERT_TRUE(mController->performComposedEffect(std::vector<CompositeEffect>(), []() {})
+ .isUnsupported());
+
+ // No reconnection attempt was made after the first one.
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestFailedApiResultResetsHalConnection) {
+ setHalExpectations(std::vector<CompositeEffect>(), vibrator::HalResult<void>::failed(),
+ vibrator::HalResult<vibrator::Capabilities>::failed(),
+ vibrator::HalResult<std::vector<Effect>>::failed(),
+ vibrator::HalResult<milliseconds>::failed());
+
+ ASSERT_EQ(0, mConnectCounter);
+
+ ASSERT_TRUE(mController->ping().isFailed());
+ ASSERT_TRUE(mController->on(10ms, []() {}).isFailed());
+ ASSERT_TRUE(mController->off().isFailed());
+ ASSERT_TRUE(mController->setAmplitude(255).isFailed());
+ ASSERT_TRUE(mController->setExternalControl(true).isFailed());
+ ASSERT_TRUE(mController->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT).isFailed());
+ ASSERT_TRUE(mController->alwaysOnDisable(1).isFailed());
+ ASSERT_TRUE(mController->getCapabilities().isFailed());
+ ASSERT_TRUE(mController->getSupportedEffects().isFailed());
+ ASSERT_TRUE(
+ mController->performEffect(Effect::CLICK, EffectStrength::LIGHT, []() {}).isFailed());
+ ASSERT_TRUE(
+ mController->performComposedEffect(std::vector<CompositeEffect>(), []() {}).isFailed());
+
+ // One reconnection attempt per api call.
+ ASSERT_EQ(11, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestMultiThreadConnectsOnlyOnce) {
+ ASSERT_EQ(0, mConnectCounter);
+
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(10))
+ .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() { ASSERT_TRUE(mController->ping().isOk()); }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+ // Connector was called only by the first thread to use the api.
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestNoHalReturnsUnsupportedAndAttemptsToReconnect) {
+ auto failingHalConnector = std::make_unique<FailingHalConnector>(&mConnectCounter);
+ mController =
+ std::make_unique<vibrator::HalController>(std::move(failingHalConnector), nullptr);
+ ASSERT_EQ(0, mConnectCounter);
+
+ ASSERT_TRUE(mController->ping().isUnsupported());
+ ASSERT_TRUE(mController->on(10ms, []() {}).isUnsupported());
+ ASSERT_TRUE(mController->off().isUnsupported());
+ ASSERT_TRUE(mController->setAmplitude(255).isUnsupported());
+ ASSERT_TRUE(mController->setExternalControl(true).isUnsupported());
+ ASSERT_TRUE(
+ mController->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT).isUnsupported());
+ ASSERT_TRUE(mController->alwaysOnDisable(1).isUnsupported());
+ ASSERT_TRUE(mController->getCapabilities().isUnsupported());
+ ASSERT_TRUE(mController->getSupportedEffects().isUnsupported());
+ ASSERT_TRUE(mController->performEffect(Effect::CLICK, EffectStrength::LIGHT, []() {})
+ .isUnsupported());
+ ASSERT_TRUE(mController->performComposedEffect(std::vector<CompositeEffect>(), []() {})
+ .isUnsupported());
+
+ // One reconnection attempt per api call.
+ ASSERT_EQ(11, mConnectCounter);
+}
+
+TEST_F(VibratorHalControllerTest, TestScheduledCallbackSurvivesReconnection) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), on(Eq(10ms), _))
+ .Times(Exactly(1))
+ .WillRepeatedly([&](milliseconds timeout, std::function<void()> callback) {
+ mMockHal.get()->getCallbackScheduler()->schedule(callback, timeout);
+ return vibrator::HalResult<void>::ok();
+ });
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(vibrator::HalResult<void>::failed()));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mController->on(10ms, callback).isOk());
+ ASSERT_TRUE(mController->ping().isFailed()); // Delete connected HAL pointer from controller.
+ mMockHal.reset(); // Delete mock HAL pointer from test class.
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ // Callback triggered even after HalWrapper was completely destroyed.
+ std::this_thread::sleep_for(15ms);
+ ASSERT_EQ(1, *callbackCounter.get());
+}