Return duration for composed effects.

Use IVibrator.getPrimitiveDuration to calculate the duration a composed
effect should have and return this result to the service.

Bug: 177807015
Test: atest libvibratorservice_test
Change-Id: I606fdb59818a13e138e1cde36172ea74050a8a75
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index bcd9957..537e49b 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -217,10 +217,10 @@
     return apply(performEffectFn, "performEffect");
 }
 
-HalResult<void> HalController::performComposedEffect(
+HalResult<milliseconds> HalController::performComposedEffect(
         const std::vector<CompositeEffect>& primitiveEffects,
         const std::function<void()>& completionCallback) {
-    hal_fn<void> performComposedEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
+    hal_fn<milliseconds> performComposedEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
         return hal->performComposedEffect(primitiveEffects, completionCallback);
     };
     return apply(performComposedEffectFn, "performComposedEffect");
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 7fee82f..6faab38 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -224,12 +224,45 @@
     return ret;
 }
 
-HalResult<void> AidlHalWrapper::performComposedEffect(
+HalResult<milliseconds> AidlHalWrapper::performComposedEffect(
         const std::vector<CompositeEffect>& primitiveEffects,
         const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
     auto cb = new HalCallbackWrapper(completionCallback);
-    return HalResult<void>::fromStatus(getHal()->compose(primitiveEffects, cb));
+    milliseconds duration(0);
+    for (const auto& effect : primitiveEffects) {
+        auto durationResult = getPrimitiveDuration(effect.primitive);
+        if (durationResult.isOk()) {
+            duration += durationResult.value();
+        }
+        duration += milliseconds(effect.delayMs);
+    }
+    return HalResult<milliseconds>::fromStatus(getHal()->compose(primitiveEffects, cb), duration);
+}
+
+HalResult<milliseconds> AidlHalWrapper::getPrimitiveDuration(CompositePrimitive primitive) {
+    std::lock_guard<std::mutex> lock(mSupportedPrimitivesMutex);
+    if (mPrimitiveDurations.empty()) {
+        constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+        constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
+        mPrimitiveDurations.resize(primitiveCount);
+    }
+    auto primitiveIdx = static_cast<size_t>(primitive);
+    if (primitiveIdx >= mPrimitiveDurations.size()) {
+        // Safety check, should not happen if enum_range is correct.
+        return HalResult<milliseconds>::unsupported();
+    }
+    auto& cache = mPrimitiveDurations[primitiveIdx];
+    if (cache.has_value()) {
+        return HalResult<milliseconds>::ok(*cache);
+    }
+    int32_t duration;
+    auto result = getHal()->getPrimitiveDuration(primitive, &duration);
+    if (result.isOk()) {
+        // Cache copy of returned value.
+        cache.emplace(duration);
+    }
+    return HalResult<milliseconds>::fromStatus(result, milliseconds(duration));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
@@ -333,10 +366,10 @@
 }
 
 template <typename I>
-HalResult<void> HidlHalWrapper<I>::performComposedEffect(const std::vector<CompositeEffect>&,
-                                                         const std::function<void()>&) {
+HalResult<std::chrono::milliseconds> HidlHalWrapper<I>::performComposedEffect(
+        const std::vector<CompositeEffect>&, const std::function<void()>&) {
     ALOGV("Skipped composed effect because Vibrator HAL AIDL is not available");
-    return HalResult<void>::unsupported();
+    return HalResult<std::chrono::milliseconds>::unsupported();
 }
 
 template <typename I>
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index c405545..16d571d 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -77,7 +77,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) final override;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) final override;
 
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 638b483..e22ad34 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -189,7 +189,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) = 0;
 
-    virtual HalResult<void> performComposedEffect(
+    virtual HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) = 0;
 
@@ -236,7 +236,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) override final;
 
@@ -252,6 +252,12 @@
             GUARDED_BY(mSupportedEffectsMutex);
     std::optional<std::vector<hardware::vibrator::CompositePrimitive>> mSupportedPrimitives
             GUARDED_BY(mSupportedPrimitivesMutex);
+    std::vector<std::optional<std::chrono::milliseconds>> mPrimitiveDurations
+            GUARDED_BY(mSupportedPrimitivesMutex);
+
+    // Loads and caches from IVibrator.
+    HalResult<std::chrono::milliseconds> getPrimitiveDuration(
+            hardware::vibrator::CompositePrimitive primitive);
 
     // Loads directly from IVibrator handle, skipping caches.
     HalResult<Capabilities> getCapabilitiesInternal();
@@ -287,7 +293,7 @@
     HalResult<std::vector<hardware::vibrator::CompositePrimitive>> getSupportedPrimitives()
             override final;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) override final;
 
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 2d9d0d6..c4b39ed 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -71,7 +71,7 @@
                 (Effect effect, EffectStrength strength,
                  const std::function<void()>& completionCallback),
                 (override));
-    MOCK_METHOD(vibrator::HalResult<void>, performComposedEffect,
+    MOCK_METHOD(vibrator::HalResult<milliseconds>, performComposedEffect,
                 (const std::vector<CompositeEffect>& primitiveEffects,
                  const std::function<void()>& completionCallback),
                 (override));
@@ -143,7 +143,7 @@
                 .WillRepeatedly(Return(durationResult));
         EXPECT_CALL(*mMockHal.get(), performComposedEffect(Eq(compositeEffects), _))
                 .Times(Exactly(cardinality))
-                .WillRepeatedly(Return(voidResult));
+                .WillRepeatedly(Return(durationResult));
 
         if (cardinality > 1) {
             // One reconnection call after each failure.
@@ -208,7 +208,10 @@
     ASSERT_TRUE(performEffectResult.isOk());
     ASSERT_EQ(100ms, performEffectResult.value());
 
-    ASSERT_TRUE(mController->performComposedEffect(compositeEffects, []() {}).isOk());
+    auto performComposedEffectResult =
+            mController->performComposedEffect(compositeEffects, []() {});
+    ASSERT_TRUE(performComposedEffectResult.isOk());
+    ASSERT_EQ(100ms, performComposedEffectResult.value());
 
     ASSERT_EQ(1, mConnectCounter);
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 96b76ba..a34cc7c 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -504,10 +504,21 @@
         EXPECT_CALL(*mMockHal.get(), compose(Eq(emptyEffects), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(
                         Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(3), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
@@ -518,6 +529,7 @@
 
     auto result = mWrapper->performComposedEffect(emptyEffects, callback);
     ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(0ms, result.value());
     ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performComposedEffect(singleEffect, callback);
@@ -530,3 +542,40 @@
     // Callback not triggered on failure
     ASSERT_EQ(1, *callbackCounter.get());
 }
+
+TEST_F(VibratorHalWrapperAidlTest, TestPerformComposedCachesPrimitiveDurationsAndIgnoresFailures) {
+    std::vector<CompositeEffect> multipleEffects;
+    multipleEffects.push_back(
+            vibrator::TestFactory::createCompositeEffect(CompositePrimitive::SPIN, 10ms, 0.5f));
+    multipleEffects.push_back(
+            vibrator::TestFactory::createCompositeEffect(CompositePrimitive::THUD, 100ms, 1.0f));
+
+    EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
+    EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
+            .Times(Exactly(2))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+    EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
+            .Times(Exactly(3))
+            .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    auto result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(111ms, result.value()); // Failed primitive duration counted as 0.
+    ASSERT_EQ(1, *callbackCounter.get());
+
+    result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(113ms, result.value()); // Second fetch succeeds and returns primitive duration.
+    ASSERT_EQ(2, *callbackCounter.get());
+
+    result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(113ms, result.value()); // Cached durations not fetched again, same duration returned.
+    ASSERT_EQ(3, *callbackCounter.get());
+}