Merge "Support task ID for fps listener rather than SurfaceControl." into sc-dev
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index ef052bd..cefcf64 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -175,8 +175,10 @@
 private:
 
     bool ReadSystemProperties() {
+        // TODO This file does not have a stable format. It should be read by
+        // code shared by init and otapreopt. See b/181182967#comment80
         static constexpr const char* kPropertyFiles[] = {
-                "/default.prop", "/system/build.prop"
+                "/system/build.prop"
         };
 
         for (size_t i = 0; i < arraysize(kPropertyFiles); ++i) {
@@ -193,28 +195,38 @@
         //   export NAME VALUE
         // For simplicity, don't respect string quotation. The values we are interested in can be
         // encoded without them.
+        // init.environ.rc and etc/classpath have the same format for
+        // environment variable exports and can be matched by the same regex.
+        // TODO Just like with the system-properties above we really should have
+        // common code between init and otapreopt to deal with reading these
+        // things. See b/181182967
+        static constexpr const char* kEnvironmentVariableSources[] = {
+                "/init.environ.rc", "/etc/classpath"
+        };
+
         std::regex export_regex("\\s*export\\s+(\\S+)\\s+(\\S+)");
-        bool parse_result = ParseFile("/init.environ.rc", [&](const std::string& line) {
-            std::smatch export_match;
-            if (!std::regex_match(line, export_match, export_regex)) {
+        for (const char* env_vars_file : kEnvironmentVariableSources) {
+            bool parse_result = ParseFile(env_vars_file, [&](const std::string& line) {
+                std::smatch export_match;
+                if (!std::regex_match(line, export_match, export_regex)) {
+                    return true;
+                }
+
+                if (export_match.size() != 3) {
+                    return true;
+                }
+
+                std::string name = export_match[1].str();
+                std::string value = export_match[2].str();
+
+                system_properties_.SetProperty(name, value);
+
                 return true;
+            });
+            if (!parse_result) {
+                return false;
             }
-
-            if (export_match.size() != 3) {
-                return true;
-            }
-
-            std::string name = export_match[1].str();
-            std::string value = export_match[2].str();
-
-            system_properties_.SetProperty(name, value);
-
-            return true;
-        });
-        if (!parse_result) {
-            return false;
         }
-
         if (system_properties_.GetProperty(kAndroidDataPathPropertyName) == nullptr) {
             return false;
         }
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 72c03bf..fb07840 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -28,6 +28,7 @@
 #include <libdm/dm.h>
 #include <selinux/android.h>
 
+#include <apex_file_repository.h>
 #include <apexd.h>
 
 #include "installd_constants.h"
@@ -64,11 +65,14 @@
     // system/apex/apexd/apexd.cpp.
     //
     // Only scan the APEX directory under /system, /system_ext and /vendor (within the chroot dir).
-    std::vector<const char*> apex_dirs{apex::kApexPackageSystemDir, apex::kApexPackageSystemExtDir,
+    std::vector<std::string> apex_dirs{apex::kApexPackageSystemDir, apex::kApexPackageSystemExtDir,
                                        apex::kApexPackageVendorDir};
+    // Initialize ApexFileRepository used internally in ScanPackagesDirAndActivate.
+    // This is a quick fix to fix apex activation in otapreopt_chroot.
+    apex::ApexFileRepository::GetInstance().AddPreInstalledApex(apex_dirs);
     for (const auto& dir : apex_dirs) {
         // Cast call to void to suppress warn_unused_result.
-        static_cast<void>(apex::ScanPackagesDirAndActivate(dir));
+        static_cast<void>(apex::ScanPackagesDirAndActivate(dir.c_str()));
     }
     return apex::GetActivePackages();
 }
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 3b46cb5..ba9ae20 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -33,6 +33,7 @@
 #include <unordered_map>
 
 #include <android-base/chrono_utils.h>
+#include <android-base/unique_fd.h>
 
 #include <binder/IBinder.h>
 #include <binder/Parcelable.h>
@@ -44,7 +45,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include <android-base/unique_fd.h>
 
 namespace android {
 class Parcel;
@@ -76,10 +76,15 @@
         uint32_t seq;
     } header;
 
-    // Body *must* be 8 byte aligned.
     // For keys and motions, rely on the fact that std::array takes up exactly as much space
     // as the underlying data. This is not guaranteed by C++, but it simplifies the conversions.
     static_assert(sizeof(std::array<uint8_t, 32>) == 32);
+
+    // For bool values, rely on the fact that they take up exactly one byte. This is not guaranteed
+    // by C++ and is implementation-dependent, but it simplifies the conversions.
+    static_assert(sizeof(bool) == 1);
+
+    // Body *must* be 8 byte aligned.
     union Body {
         struct Key {
             int32_t eventId;
@@ -154,8 +159,8 @@
         } motion;
 
         struct Finished {
-            uint32_t empty1;
-            uint32_t handled; // actually a bool, but we must maintain 8-byte alignment
+            bool handled;
+            uint8_t empty[7];
             nsecs_t consumeTime; // The time when the event was consumed by the receiving end
 
             inline size_t size() const { return sizeof(Finished); }
@@ -163,16 +168,18 @@
 
         struct Focus {
             int32_t eventId;
-            // The following two fields take up 4 bytes total
-            uint16_t hasFocus;    // actually a bool
-            uint16_t inTouchMode; // actually a bool, but we must maintain 8-byte alignment
+            // The following 3 fields take up 4 bytes total
+            bool hasFocus;
+            bool inTouchMode;
+            uint8_t empty[2];
 
             inline size_t size() const { return sizeof(Focus); }
         } focus;
 
         struct Capture {
             int32_t eventId;
-            uint32_t pointerCaptureEnabled; // actually a bool, but we maintain 8-byte alignment
+            bool pointerCaptureEnabled;
+            uint8_t empty[3];
 
             inline size_t size() const { return sizeof(Capture); }
         } capture;
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index e9d866b..6df04f0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -216,6 +216,11 @@
         "performance*",
         "portability*",
     ],
+
+    pgo: {
+        sampling: true,
+        profile_file: "libbinder/libbinder.profdata",
+    },
 }
 
 // AIDL interface between libbinder and framework.jar
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 1e47a3c..ee2daec 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -577,8 +577,8 @@
     msg.header.type = InputMessage::Type::FOCUS;
     msg.header.seq = seq;
     msg.body.focus.eventId = eventId;
-    msg.body.focus.hasFocus = hasFocus ? 1 : 0;
-    msg.body.focus.inTouchMode = inTouchMode ? 1 : 0;
+    msg.body.focus.hasFocus = hasFocus;
+    msg.body.focus.inTouchMode = inTouchMode;
     return mChannel->sendMessage(&msg);
 }
 
@@ -595,7 +595,7 @@
     msg.header.type = InputMessage::Type::CAPTURE;
     msg.header.seq = seq;
     msg.body.capture.eventId = eventId;
-    msg.body.capture.pointerCaptureEnabled = pointerCaptureEnabled ? 1 : 0;
+    msg.body.capture.pointerCaptureEnabled = pointerCaptureEnabled;
     return mChannel->sendMessage(&msg);
 }
 
@@ -615,7 +615,7 @@
               mChannel->getName().c_str(), NamedEnum::string(msg.header.type).c_str());
         return UNKNOWN_ERROR;
     }
-    callback(msg.header.seq, msg.body.finished.handled == 1, msg.body.finished.consumeTime);
+    callback(msg.header.seq, msg.body.finished.handled, msg.body.finished.consumeTime);
     return OK;
 }
 
@@ -1168,7 +1168,7 @@
     InputMessage msg;
     msg.header.type = InputMessage::Type::FINISHED;
     msg.header.seq = seq;
-    msg.body.finished.handled = handled ? 1 : 0;
+    msg.body.finished.handled = handled;
     msg.body.finished.consumeTime = getConsumeTime(seq);
     status_t result = mChannel->sendMessage(&msg);
     if (result == OK) {
@@ -1228,12 +1228,12 @@
 }
 
 void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus == 1,
-                      msg->body.focus.inTouchMode == 1);
+    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus,
+                      msg->body.focus.inTouchMode);
 }
 
 void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled == 1);
+    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled);
 }
 
 void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index a886585..8f43608 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -49,6 +49,7 @@
   CHECK_OFFSET(InputMessage::Body::Key, downTime, 88);
 
   CHECK_OFFSET(InputMessage::Body::Motion, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty1, 4);
   CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Motion, source, 20);
@@ -60,6 +61,7 @@
   CHECK_OFFSET(InputMessage::Body::Motion, metaState, 72);
   CHECK_OFFSET(InputMessage::Body::Motion, buttonState, 76);
   CHECK_OFFSET(InputMessage::Body::Motion, classification, 80);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty2, 81);
   CHECK_OFFSET(InputMessage::Body::Motion, edgeFlags, 84);
   CHECK_OFFSET(InputMessage::Body::Motion, downTime, 88);
   CHECK_OFFSET(InputMessage::Body::Motion, dsdx, 96);
@@ -73,16 +75,20 @@
   CHECK_OFFSET(InputMessage::Body::Motion, xCursorPosition, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, yCursorPosition, 132);
   CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty3, 140);
   CHECK_OFFSET(InputMessage::Body::Motion, pointers, 144);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
-  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 6);
+  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 5);
+  CHECK_OFFSET(InputMessage::Body::Focus, empty, 6);
 
   CHECK_OFFSET(InputMessage::Body::Capture, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Capture, pointerCaptureEnabled, 4);
+  CHECK_OFFSET(InputMessage::Body::Capture, empty, 5);
 
-  CHECK_OFFSET(InputMessage::Body::Finished, handled, 4);
+  CHECK_OFFSET(InputMessage::Body::Finished, handled, 0);
+  CHECK_OFFSET(InputMessage::Body::Finished, empty, 1);
   CHECK_OFFSET(InputMessage::Body::Finished, consumeTime, 8);
 }
 
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index c29181d..d38f2ef 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -22,6 +22,8 @@
 #pragma GCC diagnostic ignored "-Wunused-parameter"
 #include <EGL/Platform.h>
 #pragma GCC diagnostic pop
+
+#include <android-base/properties.h>
 #include <android/dlext.h>
 #include <dlfcn.h>
 #include <graphicsenv/GraphicsEnv.h>
@@ -33,7 +35,6 @@
 
 namespace angle {
 
-constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
 constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
 
 static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
@@ -107,18 +108,36 @@
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
     void* so = nullptr;
     if (ns) {
+        // Loading from an APK, so hard-code the suffix to "_angle".
+        constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
         const android_dlextinfo dlextinfo = {
                 .flags = ANDROID_DLEXT_USE_NAMESPACE,
                 .library_namespace = ns,
         };
         so = android_dlopen_ext(kAngleEs2Lib, kAngleDlFlags, &dlextinfo);
+        if (so) {
+            ALOGD("dlopen_ext from APK (%s) success at %p", kAngleEs2Lib, so);
+        } else {
+            ALOGE("dlopen_ext(\"%s\") failed: %s", kAngleEs2Lib, dlerror());
+            return false;
+        }
     } else {
         // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
-        so = android_load_sphal_library(kAngleEs2Lib, kAngleDlFlags);
-    }
-    if (!so) {
-        ALOGE("%s failed to dlopen %s!", __FUNCTION__, kAngleEs2Lib);
-        return false;
+        // Get the specified ANGLE library filename suffix.
+        std::string angleEs2LibSuffix = android::base::GetProperty("ro.hardware.egl", "");
+        if (angleEs2LibSuffix.empty()) {
+            ALOGE("%s failed to get valid ANGLE library filename suffix!", __FUNCTION__);
+            return false;
+        }
+
+        std::string angleEs2LibName = "libGLESv2_" + angleEs2LibSuffix + ".so";
+        so = android_load_sphal_library(angleEs2LibName.c_str(), kAngleDlFlags);
+        if (so) {
+            ALOGD("dlopen (%s) success at %p", angleEs2LibName.c_str(), so);
+        } else {
+            ALOGE("%s failed to dlopen %s!", __FUNCTION__, angleEs2LibName.c_str());
+            return false;
+        }
     }
 
     angleGetDisplayPlatform =
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index d5bf569..325361b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -39,9 +39,6 @@
 using ::testing::StrictMock;
 
 struct CompositionEngineTest : public testing::Test {
-    android::mock::HWComposer* mHwc = new StrictMock<android::mock::HWComposer>();
-    renderengine::mock::RenderEngine* mRenderEngine =
-            new StrictMock<renderengine::mock::RenderEngine>();
     std::shared_ptr<TimeStats> mTimeStats;
 
     impl::CompositionEngine mEngine;
@@ -58,15 +55,18 @@
 }
 
 TEST_F(CompositionEngineTest, canSetHWComposer) {
-    mEngine.setHwComposer(std::unique_ptr<android::HWComposer>(mHwc));
+    android::mock::HWComposer* hwc = new StrictMock<android::mock::HWComposer>();
+    mEngine.setHwComposer(std::unique_ptr<android::HWComposer>(hwc));
 
-    EXPECT_EQ(mHwc, &mEngine.getHwComposer());
+    EXPECT_EQ(hwc, &mEngine.getHwComposer());
 }
 
 TEST_F(CompositionEngineTest, canSetRenderEngine) {
-    mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    renderengine::mock::RenderEngine* renderEngine =
+            new StrictMock<renderengine::mock::RenderEngine>();
+    mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(renderEngine));
 
-    EXPECT_EQ(mRenderEngine, &mEngine.getRenderEngine());
+    EXPECT_EQ(renderEngine, &mEngine.getRenderEngine());
 }
 
 TEST_F(CompositionEngineTest, canSetTimeStats) {
@@ -130,10 +130,10 @@
 struct CompositionEngineUpdateCursorAsyncTest : public CompositionEngineTest {
 public:
     struct Layer {
-        Layer() { EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE)); }
+        Layer() { EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*layerFE)); }
 
         StrictMock<mock::OutputLayer> outputLayer;
-        StrictMock<mock::LayerFE> layerFE;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState layerFEState;
     };
 
@@ -175,21 +175,21 @@
     {
         InSequence seq;
         EXPECT_CALL(mOutput2Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput2Layer1.outputLayer, writeCursorPositionToHWC());
     }
 
     {
         InSequence seq;
         EXPECT_CALL(mOutput3Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput3Layer1.outputLayer, writeCursorPositionToHWC());
     }
 
     {
         InSequence seq;
         EXPECT_CALL(mOutput3Layer2.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput3Layer2.outputLayer, writeCursorPositionToHWC());
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 3059beb..5f0b0ee 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1697,12 +1697,12 @@
 
     struct Layer {
         Layer() {
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
     };
 
@@ -2712,12 +2712,12 @@
 
     struct Layer {
         Layer() {
-            EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE));
+            EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*layerFE));
             EXPECT_CALL(outputLayer, getHwcLayer()).WillRepeatedly(Return(&hwc2Layer));
         }
 
         StrictMock<mock::OutputLayer> outputLayer;
-        StrictMock<mock::LayerFE> layerFE;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
         StrictMock<HWC2::mock::Layer> hwc2Layer;
     };
 
@@ -2793,11 +2793,11 @@
     // are passed. This happens to work with the current implementation, but
     // would not survive certain calls like Fence::merge() which would return a
     // new instance.
-    EXPECT_CALL(mLayer1.layerFE,
+    EXPECT_CALL(*mLayer1.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer1Fence.get()))));
-    EXPECT_CALL(mLayer2.layerFE,
+    EXPECT_CALL(*mLayer2.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer2Fence.get()))));
-    EXPECT_CALL(mLayer3.layerFE,
+    EXPECT_CALL(*mLayer3.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer3Fence.get()))));
 
     mOutput.postFramebuffer();
@@ -2824,9 +2824,9 @@
     // Fence::merge is called, and since none of the fences are actually valid,
     // Fence::NO_FENCE is returned and passed to each onLayerDisplayed() call.
     // This is the best we can do without creating a real kernel fence object.
-    EXPECT_CALL(mLayer1.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(mLayer2.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(mLayer3.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(Fence::NO_FENCE));
 
     mOutput.postFramebuffer();
 }
@@ -3330,12 +3330,12 @@
 struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeSurfacesTest {
     struct Layer {
         Layer() {
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
     };
 
@@ -3546,12 +3546,12 @@
         Layer() {
             EXPECT_CALL(mOutputLayer, getState()).WillRepeatedly(ReturnRef(mOutputLayerState));
             EXPECT_CALL(mOutputLayer, editState()).WillRepeatedly(ReturnRef(mOutputLayerState));
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
         impl::OutputLayerCompositionState mOutputLayerState;
         LayerFE::LayerSettings mLayerSettings;
@@ -3645,11 +3645,11 @@
     LayerFE::LayerSettings mShadowSettings;
     mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[1].mLayerSettings})));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
@@ -3683,7 +3683,7 @@
     mLayers[1].mLayerFEState.isOpaque = true;
     mLayers[2].mLayerFEState.isOpaque = true;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
@@ -3709,7 +3709,7 @@
     mLayers[1].mLayerFEState.isOpaque = false;
     mLayers[2].mLayerFEState.isOpaque = false;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
@@ -3773,9 +3773,9 @@
     mBlackoutSettings.alpha = 0.f;
     mBlackoutSettings.disableBlending = true;
 
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mBlackoutSettings})));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3835,11 +3835,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -3891,11 +3891,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -3948,11 +3948,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -4003,11 +4003,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -4056,11 +4056,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(mOutput.generateClientCompositionRequests(true /* supportsProtectedContent */,
@@ -4188,7 +4188,7 @@
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings))))
+    EXPECT_CALL(*leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({leftLayer.mLayerSettings})));
 
     compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{
@@ -4206,7 +4206,7 @@
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings))))
+    EXPECT_CALL(*rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({rightLayer.mLayerSettings})));
 
     constexpr bool supportsProtectedContent = true;
@@ -4246,7 +4246,7 @@
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mShadowSettings})));
 
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -4286,7 +4286,7 @@
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 7515872..03e38f3 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -207,6 +207,17 @@
     }
 }
 
+FrameTimelineEvent::PredictionType toProto(PredictionState predictionState) {
+    switch (predictionState) {
+        case PredictionState::Valid:
+            return FrameTimelineEvent::PREDICTION_VALID;
+        case PredictionState::Expired:
+            return FrameTimelineEvent::PREDICTION_EXPIRED;
+        case PredictionState::None:
+            return FrameTimelineEvent::PREDICTION_UNKNOWN;
+    }
+}
+
 int32_t jankTypeBitmaskToProto(int32_t jankType) {
     if (jankType == JankType::None) {
         return FrameTimelineEvent::JANK_NONE;
@@ -628,6 +639,7 @@
                                                          FrameReadyMetadata::OnTimeFinish);
         actualSurfaceFrameStartEvent->set_gpu_composition(mGpuComposition);
         actualSurfaceFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+        actualSurfaceFrameStartEvent->set_prediction_type(toProto(mPredictionState));
     });
 
     // Actual timeline end
@@ -968,6 +980,7 @@
                                                          FrameReadyMetadata::OnTimeFinish);
         actualDisplayFrameStartEvent->set_gpu_composition(mGpuComposition);
         actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+        actualDisplayFrameStartEvent->set_prediction_type(toProto(mPredictionState));
     });
 
     // Actual timeline end
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 6150b22..f2cb951 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -40,6 +40,7 @@
 using ProtoFrameEnd = perfetto::protos::FrameTimelineEvent_FrameEnd;
 using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
 using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
+using ProtoPredictionType = perfetto::protos::FrameTimelineEvent_PredictionType;
 
 namespace android::frametimeline {
 
@@ -717,7 +718,7 @@
 
 ProtoActualDisplayFrameStart createProtoActualDisplayFrameStart(
         int64_t cookie, int64_t token, pid_t pid, ProtoPresentType presentType, bool onTimeFinish,
-        bool gpuComposition, ProtoJankType jankType) {
+        bool gpuComposition, ProtoJankType jankType, ProtoPredictionType predictionType) {
     ProtoActualDisplayFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -726,6 +727,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_prediction_type(predictionType);
     return proto;
 }
 
@@ -745,7 +747,7 @@
 ProtoActualSurfaceFrameStart createProtoActualSurfaceFrameStart(
         int64_t cookie, int64_t token, int64_t displayFrameToken, pid_t pid, std::string layerName,
         ProtoPresentType presentType, bool onTimeFinish, bool gpuComposition,
-        ProtoJankType jankType) {
+        ProtoJankType jankType, ProtoPredictionType predictionType) {
     ProtoActualSurfaceFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -756,6 +758,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_prediction_type(predictionType);
     return proto;
 }
 
@@ -796,6 +799,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_prediction_type());
+    EXPECT_EQ(received.prediction_type(), source.prediction_type());
 }
 
 void validateTraceEvent(const ProtoExpectedSurfaceFrameStart& received,
@@ -841,6 +846,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_prediction_type());
+    EXPECT_EQ(received.prediction_type(), source.prediction_type());
 }
 
 void validateTraceEvent(const ProtoFrameEnd& received, const ProtoFrameEnd& source) {
@@ -869,7 +876,8 @@
             createProtoActualDisplayFrameStart(traceCookie + 2, displayFrameToken1,
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 2);
 
     addEmptyDisplayFrame();
@@ -944,7 +952,8 @@
             createProtoActualDisplayFrameStart(traceCookie + 1, displayFrameToken1,
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN);
+                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     addEmptyDisplayFrame();
@@ -1014,7 +1023,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 2, surfaceFrameToken,
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_DROPPED, false, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2);
 
     auto protoPresentedSurfaceFrameExpectedStart =
@@ -1025,7 +1035,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 4, surfaceFrameToken,
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
 
     // Set up the display frame
@@ -1167,7 +1178,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN);
+                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
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 f85fa10..8b5caa5 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -506,10 +506,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)));
@@ -520,6 +531,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);
@@ -532,3 +544,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());
+}