Merge "SkiaRE: refactor to use eglQueryString instead"
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 0377075..88a631a 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -164,12 +164,8 @@
     ALOGE("Invalid object type 0x%08x", obj.hdr.type);
 }
 
-status_t Parcel::finishFlattenBinder(
-    const sp<IBinder>& binder, const flat_binder_object& flat)
+status_t Parcel::finishFlattenBinder(const sp<IBinder>& binder)
 {
-    status_t status = writeObject(flat, false);
-    if (status != OK) return status;
-
     internal::Stability::tryMarkCompilationUnit(binder.get());
     auto category = internal::Stability::getCategory(binder.get());
     return writeInt32(category.repr());
@@ -238,7 +234,10 @@
 
     obj.flags |= schedBits;
 
-    return finishFlattenBinder(binder, obj);
+    status_t status = writeObject(obj, false);
+    if (status != OK) return status;
+
+    return finishFlattenBinder(binder);
 }
 
 status_t Parcel::unflattenBinder(sp<IBinder>* out) const
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 83ca687..9aedf28 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -130,7 +130,7 @@
 
     // The root object is special since we get it directly from the driver, it is never
     // written by Parcell::writeStrongBinder.
-    internal::Stability::tryMarkCompilationUnit(context.get());
+    internal::Stability::markCompilationUnit(context.get());
 
     return context;
 }
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1cfb560..4375818 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -56,6 +56,9 @@
     },
     {
       "name": "rustBinderTest"
+    },
+    {
+      "name": "binderRustNdkInteropTest"
     }
   ]
 }
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index cfe1f3a..9f5260a 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -522,8 +522,7 @@
     status_t            validateReadData(size_t len) const;
     void                updateWorkSourceRequestHeaderPosition() const;
 
-    status_t            finishFlattenBinder(const sp<IBinder>& binder,
-                                            const flat_binder_object& flat);
+    status_t            finishFlattenBinder(const sp<IBinder>& binder);
     status_t            finishUnflattenBinder(const sp<IBinder>& binder, sp<IBinder>* out) const;
     status_t            flattenBinder(const sp<IBinder>& binder);
     status_t            unflattenBinder(sp<IBinder>* out) const;
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 037ee95..b558f27 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -21,7 +21,8 @@
 use crate::proxy::{DeathRecipient, SpIBinder};
 use crate::sys;
 
-use std::ffi::{c_void, CString};
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
 use std::os::unix::io::AsRawFd;
 use std::ptr;
 
@@ -205,6 +206,22 @@
     pub(crate) unsafe fn from_ptr(ptr: *const sys::AIBinder_Class) -> InterfaceClass {
         InterfaceClass(ptr)
     }
+
+    /// Get the interface descriptor string of this class.
+    pub fn get_descriptor(&self) -> String {
+        unsafe {
+            // SAFETY: The descriptor returned by AIBinder_Class_getDescriptor
+            // is always a two-byte null terminated sequence of u16s. Thus, we
+            // can continue reading from the pointer until we hit a null value,
+            // and this pointer can be a valid slice if the slice length is <=
+            // the number of u16 elements before the null terminator.
+
+            let raw_descriptor: *const c_char = sys::AIBinder_Class_getDescriptor(self.0);
+            CStr::from_ptr(raw_descriptor).to_str()
+                .expect("Expected valid UTF-8 string from AIBinder_Class_getDescriptor")
+                .into()
+        }
+    }
 }
 
 impl From<InterfaceClass> for *const sys::AIBinder_Class {
@@ -507,12 +524,7 @@
             }
 
             fn from_binder(mut binder: $crate::SpIBinder) -> $crate::Result<Self> {
-                use $crate::AssociateClass;
-                if binder.associate_class(<$native as $crate::Remotable>::get_class()) {
-                    Ok(Self { binder, $($fname: $finit),* })
-                } else {
-                    Err($crate::StatusCode::BAD_TYPE)
-                }
+                Ok(Self { binder, $($fname: $finit),* })
             }
         }
 
@@ -567,16 +579,33 @@
         impl $crate::FromIBinder for dyn $interface {
             fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<Box<dyn $interface>> {
                 use $crate::AssociateClass;
-                if !ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
-                    return Err($crate::StatusCode::BAD_TYPE.into());
+
+                let existing_class = ibinder.get_class();
+                if let Some(class) = existing_class {
+                    if class != <$native as $crate::Remotable>::get_class() &&
+                        class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor()
+                    {
+                        // The binder object's descriptor string matches what we
+                        // expect. We still need to treat this local or already
+                        // associated object as remote, because we can't cast it
+                        // into a Rust service object without a matching class
+                        // pointer.
+                        return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+                    }
+                } else if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
+                    let service: $crate::Result<$crate::Binder<$native>> =
+                        std::convert::TryFrom::try_from(ibinder.clone());
+                    if let Ok(service) = service {
+                        // We were able to associate with our expected class and
+                        // the service is local.
+                        return Ok(Box::new(service));
+                    } else {
+                        // Service is remote
+                        return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+                    }
                 }
 
-                let service: $crate::Result<$crate::Binder<$native>> = std::convert::TryFrom::try_from(ibinder.clone());
-                if let Ok(service) = service {
-                    Ok(Box::new(service))
-                } else {
-                    Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?))
-                }
+                Err($crate::StatusCode::BAD_TYPE.into())
             }
         }
 
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 5002fc6..485bb42 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -91,7 +91,7 @@
 
     /// Return the interface class of this binder object, if associated with
     /// one.
-    pub(crate) fn get_class(&mut self) -> Option<InterfaceClass> {
+    pub fn get_class(&mut self) -> Option<InterfaceClass> {
         unsafe {
             // Safety: `SpIBinder` guarantees that it always contains a valid
             // `AIBinder` pointer. `AIBinder_getClass` returns either a null
diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp
index 3db40ba..5ae9c53 100644
--- a/libs/binder/rust/tests/Android.bp
+++ b/libs/binder/rust/tests/Android.bp
@@ -30,3 +30,52 @@
     auto_gen_config: false,
     test_suites: ["general-tests"],
 }
+
+cc_test {
+    name: "binderRustNdkInteropTest",
+    srcs: [
+        "binderRustNdkInteropTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "IBinderRustNdkInteropTest-ndk_platform",
+        "libbinder_ndk_rust_interop",
+    ],
+    test_suites: ["general-tests"],
+    require_root: true,
+
+    // rustBinderTestService uses a custom config
+    auto_gen_config: true,
+}
+
+aidl_interface {
+    name: "IBinderRustNdkInteropTest",
+    unstable: true,
+    srcs: [
+        "IBinderRustNdkInteropTest.aidl",
+        "IBinderRustNdkInteropTestOther.aidl",
+    ],
+    backend: {
+        ndk: {
+            enabled: true,
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
+
+rust_ffi_static {
+    name: "libbinder_ndk_rust_interop",
+    crate_name: "binder_ndk_rust_interop",
+    srcs: [
+        "ndk_rust_interop.rs",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+        "IBinderRustNdkInteropTest-rust",
+    ],
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
new file mode 100644
index 0000000..7f5e837
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+interface IBinderRustNdkInteropTest {
+    @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
new file mode 100644
index 0000000..82a0323
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+interface IBinderRustNdkInteropTestOther {
+    @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/binderRustNdkInteropTest.cpp b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
new file mode 100644
index 0000000..59ca6ed
--- /dev/null
+++ b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include <aidl/BnBinderRustNdkInteropTest.h>
+#include <aidl/IBinderRustNdkInteropTest.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/Status.h>
+#include <gtest/gtest.h>
+
+using namespace android;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using ::ndk::SpAIBinder;
+
+static const char* kNdkServerName = "NdkServer-BinderRustNdkInteropTest";
+static const char* kRustServerName = "RustServer-BinderRustNdkInteropTest";
+
+extern "C" {
+int rust_call_ndk(const char* service_name);
+int rust_start_service(const char* service_name);
+}
+
+class NdkServer : public aidl::BnBinderRustNdkInteropTest {
+    ScopedAStatus echo(const std::string& in, std::string* out) override {
+        *out = in;
+        return ScopedAStatus::ok();
+    }
+};
+
+TEST(RustNdkInterop, RustCanCallNdk) {
+    ASSERT_EQ(STATUS_OK, rust_call_ndk(kNdkServerName));
+}
+
+TEST(RustNdkInterop, NdkCanCallRust) {
+    ASSERT_EQ(STATUS_OK, rust_start_service(kRustServerName));
+
+    SpAIBinder binder = SpAIBinder(AServiceManager_checkService(kRustServerName));
+    ASSERT_NE(nullptr, binder.get());
+    EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
+
+    auto interface = aidl::IBinderRustNdkInteropTest::fromBinder(binder);
+    // TODO(b/167723746): this test requires that fromBinder allow association
+    // with an already associated local binder by treating it as remote.
+    EXPECT_EQ(interface, nullptr);
+
+    // std::string in("testing");
+    // std::string out;
+    // EXPECT_TRUE(interface->echo(in, &out).isOk());
+    // EXPECT_EQ(in, out);
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    // so we can host a client and service concurrently
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+
+    std::shared_ptr<NdkServer> ndkServer = SharedRefBase::make<NdkServer>();
+    EXPECT_EQ(STATUS_OK, AServiceManager_addService(ndkServer->asBinder().get(), kNdkServerName));
+
+    return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 953d328..497361a 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -173,6 +173,30 @@
     }
 }
 
+/// Trivial testing binder interface
+pub trait ITestSameDescriptor: Interface {}
+
+declare_binder_interface! {
+    ITestSameDescriptor["android.os.ITest"] {
+        native: BnTestSameDescriptor(on_transact_same_descriptor),
+        proxy: BpTestSameDescriptor,
+    }
+}
+
+fn on_transact_same_descriptor(
+    _service: &dyn ITestSameDescriptor,
+    _code: TransactionCode,
+    _data: &Parcel,
+    _reply: &mut Parcel,
+) -> binder::Result<()> {
+    Ok(())
+}
+
+impl ITestSameDescriptor for BpTestSameDescriptor {}
+
+impl ITestSameDescriptor for Binder<BnTestSameDescriptor> {}
+
+
 #[cfg(test)]
 mod tests {
     use selinux_bindgen as selinux_sys;
@@ -185,9 +209,9 @@
     use std::thread;
     use std::time::Duration;
 
-    use binder::{DeathRecipient, FromIBinder, IBinder, SpIBinder, StatusCode};
+    use binder::{Binder, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode};
 
-    use super::{ITest, RUST_SERVICE_BINARY};
+    use super::{BnTest, ITest, ITestSameDescriptor, RUST_SERVICE_BINARY, TestService};
 
     pub struct ScopedServiceProcess(Child);
 
@@ -435,4 +459,27 @@
             assert_eq!(extension.test().unwrap(), extension_name);
         }
     }
+
+    /// Test re-associating a local binder object with a different class.
+    ///
+    /// This is needed because different binder service (e.g. NDK vs Rust)
+    /// implementations are incompatible and must not be interchanged. A local
+    /// service with the same descriptor string but a different class pointer
+    /// may have been created by an NDK service and is therefore incompatible
+    /// with the Rust service implementation. It must be treated as remote and
+    /// all API calls parceled and sent through transactions.
+    ///
+    /// Further tests of this behavior with the C NDK and Rust API are in
+    /// rust_ndk_interop.rs
+    #[test]
+    fn associate_existing_class() {
+        let service = Binder::new(BnTest(Box::new(TestService {
+            s: "testing_service".to_string(),
+        })));
+
+        // This should succeed although we will have to treat the service as
+        // remote.
+        let _interface: Box<dyn ITestSameDescriptor> = FromIBinder::try_from(service.as_binder())
+            .expect("Could not re-interpret service as the ITestSameDescriptor interface");
+    }
 }
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
new file mode 100644
index 0000000..70a6dc0
--- /dev/null
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+//! Rust Binder NDK interop tests
+
+use std::ffi::CStr;
+use std::os::raw::{c_char, c_int};
+use ::IBinderRustNdkInteropTest::binder::{self, Interface, StatusCode};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTest::{
+    BnBinderRustNdkInteropTest, IBinderRustNdkInteropTest,
+};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTestOther::{
+    IBinderRustNdkInteropTestOther,
+};
+
+/// Look up the provided AIDL service and call its echo method.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_call_ndk(service_name: *const c_char) -> c_int {
+    let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+
+    // The Rust class descriptor pointer will not match the NDK one, but the
+    // descriptor strings match so this needs to still associate.
+    let service: Box<dyn IBinderRustNdkInteropTest> = match binder::get_interface(service_name) {
+        Err(e) => {
+            eprintln!("Could not find Ndk service {}: {:?}", service_name, e);
+            return StatusCode::NAME_NOT_FOUND as c_int;
+        }
+        Ok(service) => service,
+    };
+
+    match service.echo("testing") {
+        Ok(s) => if s != "testing" {
+            return StatusCode::BAD_VALUE as c_int;
+        },
+        Err(e) => return e.into(),
+    }
+
+    // Try using the binder service through the wrong interface type
+    let wrong_service: Result<Box<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
+        binder::get_interface(service_name);
+    match wrong_service {
+        Err(e) if e == StatusCode::BAD_TYPE => {}
+        Err(e) => {
+            eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
+            return e as c_int;
+        }
+        Ok(_) => {
+            eprintln!("We should not be allowed to use a service via the wrong interface");
+            return StatusCode::BAD_TYPE as c_int;
+        }
+    }
+
+    StatusCode::OK as c_int
+}
+
+struct Service;
+
+impl Interface for Service {}
+
+impl IBinderRustNdkInteropTest for Service {
+    fn echo(&self, s: &str) -> binder::Result<String> {
+        Ok(s.to_string())
+    }
+}
+
+/// Start the interop Echo test service with the given service name.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_start_service(service_name: *const c_char) -> c_int {
+    let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+    let service = BnBinderRustNdkInteropTest::new_binder(Service);
+    match binder::add_service(&service_name, service.as_binder()) {
+        Ok(_) => StatusCode::OK as c_int,
+        Err(e) => e as c_int,
+    }
+}
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index e6aa02a..3a3a96f 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -289,6 +289,11 @@
     t->setDesiredPresentTime(bufferItem.mTimestamp);
     t->setFrameNumber(mSurfaceControl, bufferItem.mFrameNumber);
 
+    if (!mNextFrameTimelineVsyncIdQueue.empty()) {
+        t->setFrameTimelineVsync(mSurfaceControl, mNextFrameTimelineVsyncIdQueue.front());
+        mNextFrameTimelineVsyncIdQueue.pop();
+    }
+
     if (mAutoRefresh != bufferItem.mAutoRefresh) {
         t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh);
         mAutoRefresh = bufferItem.mAutoRefresh;
@@ -417,10 +422,8 @@
 
 status_t BLASTBufferQueue::setFrameTimelineVsync(int64_t frameTimelineVsyncId) {
     std::unique_lock _lock{mMutex};
-    SurfaceComposerClient::Transaction t;
-
-    return t.setFrameTimelineVsync(mSurfaceControl, frameTimelineVsyncId)
-        .apply();
+    mNextFrameTimelineVsyncIdQueue.push(frameTimelineVsyncId);
+    return OK;
 }
 
 sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 9fb7d6f..bdf128a 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -28,6 +28,7 @@
 
 #include <system/window.h>
 #include <thread>
+#include <queue>
 
 namespace android {
 
@@ -143,6 +144,8 @@
     // should acquire the next frame as soon as it can and not wait for a frame to become available.
     // This is only relevant for shared buffer mode.
     bool mAutoRefresh GUARDED_BY(mMutex) = false;
+
+    std::queue<int64_t> mNextFrameTimelineVsyncIdQueue GUARDED_BY(mMutex);
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index e075d3e..1e6d21e 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -14,5 +14,8 @@
         "libui",
         "libutils",
     ],
+    static_libs: [
+        "libperfetto_client_experimental",
+    ],
     export_include_dirs: ["."],
 }
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index bd87482..ef5f5ad 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -29,6 +29,7 @@
 namespace android::frametimeline::impl {
 
 using base::StringAppendF;
+using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
 
 void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
                const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
@@ -143,6 +144,32 @@
                            });
 }
 
+FrameTimelineEvent::PresentType presentTypeToProto(int32_t jankMetadata) {
+    if (jankMetadata & EarlyPresent) {
+        return FrameTimelineEvent::PRESENT_EARLY;
+    }
+    if (jankMetadata & LatePresent) {
+        return FrameTimelineEvent::PRESENT_LATE;
+    }
+    return FrameTimelineEvent::PRESENT_ON_TIME;
+}
+
+FrameTimelineEvent::JankType JankTypeToProto(TimeStats::JankType jankType) {
+    switch (jankType) {
+        case TimeStats::None:
+            return FrameTimelineEvent::JANK_NONE;
+        case TimeStats::Display:
+            return FrameTimelineEvent::JANK_DISPLAY_HAL;
+        case TimeStats::SurfaceFlingerDeadlineMissed:
+            return FrameTimelineEvent::JANK_SF_DEADLINE_MISSED;
+        case TimeStats::AppDeadlineMissed:
+        case TimeStats::PredictionExpired:
+            return FrameTimelineEvent::JANK_APP_DEADLINE_MISSED;
+        default:
+            return FrameTimelineEvent::JANK_UNKNOWN;
+    }
+}
+
 int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
     ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mMutex);
@@ -177,10 +204,11 @@
     }
 }
 
-SurfaceFrame::SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName,
+SurfaceFrame::SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
                            std::string debugName, PredictionState predictionState,
                            frametimeline::TimelineItem&& predictions)
-      : mOwnerPid(ownerPid),
+      : mToken(token),
+        mOwnerPid(ownerPid),
         mOwnerUid(ownerUid),
         mLayerName(std::move(layerName)),
         mDebugName(std::move(debugName)),
@@ -291,17 +319,70 @@
     dumpTable(result, mPredictions, mActuals, indent, mPredictionState, baseTime);
 }
 
+void SurfaceFrame::traceSurfaceFrame(int64_t displayFrameToken) {
+    using FrameTimelineDataSource = FrameTimeline::FrameTimelineDataSource;
+    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+            ALOGD("Cannot trace SurfaceFrame - %s with invalid token", mLayerName.c_str());
+            return;
+        } else if (displayFrameToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+            ALOGD("Cannot trace SurfaceFrame  - %s with invalid displayFrameToken",
+                  mLayerName.c_str());
+            return;
+        }
+        auto packet = ctx.NewTracePacket();
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        packet->set_timestamp(static_cast<uint64_t>(systemTime()));
+
+        auto* event = packet->set_frame_timeline_event();
+        auto* surfaceFrameEvent = event->set_surface_frame();
+
+        surfaceFrameEvent->set_token(mToken);
+        surfaceFrameEvent->set_display_frame_token(displayFrameToken);
+
+        if (mPresentState == PresentState::Dropped) {
+            surfaceFrameEvent->set_present_type(FrameTimelineEvent::PRESENT_DROPPED);
+        } else if (mPresentState == PresentState::Unknown) {
+            surfaceFrameEvent->set_present_type(FrameTimelineEvent::PRESENT_UNSPECIFIED);
+        } else {
+            surfaceFrameEvent->set_present_type(presentTypeToProto(mJankMetadata));
+        }
+        surfaceFrameEvent->set_on_time_finish(!(mJankMetadata & LateFinish));
+        surfaceFrameEvent->set_gpu_composition(mJankMetadata & GpuComposition);
+        surfaceFrameEvent->set_jank_type(JankTypeToProto(mJankType));
+
+        surfaceFrameEvent->set_expected_start_ns(mPredictions.startTime);
+        surfaceFrameEvent->set_expected_end_ns(mPredictions.endTime);
+
+        surfaceFrameEvent->set_actual_start_ns(mActuals.startTime);
+        surfaceFrameEvent->set_actual_end_ns(mActuals.endTime);
+
+        surfaceFrameEvent->set_layer_name(mDebugName);
+        surfaceFrameEvent->set_pid(mOwnerPid);
+    });
+}
+
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
       : mCurrentDisplayFrame(std::make_shared<DisplayFrame>()),
         mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)) {}
 
+void FrameTimeline::onBootFinished() {
+    perfetto::TracingInitArgs args;
+    args.backends = perfetto::kSystemBackend;
+    perfetto::Tracing::Initialize(args);
+    registerDataSource();
+}
+
+void FrameTimeline::registerDataSource() {
+    perfetto::DataSourceDescriptor dsd;
+    dsd.set_name(kFrameTimelineDataSource);
+    FrameTimelineDataSource::Register(dsd);
+}
+
 FrameTimeline::DisplayFrame::DisplayFrame()
-      : surfaceFlingerPredictions(TimelineItem()),
-        surfaceFlingerActuals(TimelineItem()),
-        predictionState(PredictionState::None),
-        jankType(TimeStats::JankType::None),
-        jankMetadata(0) {
+      : surfaceFlingerPredictions(TimelineItem()), surfaceFlingerActuals(TimelineItem()) {
     this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
 }
 
@@ -310,17 +391,19 @@
         std::optional<int64_t> token) {
     ATRACE_CALL();
     if (!token) {
-        return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
+        return std::make_unique<impl::SurfaceFrame>(ISurfaceComposer::INVALID_VSYNC_ID, ownerPid,
+                                                    ownerUid, std::move(layerName),
                                                     std::move(debugName), PredictionState::None,
                                                     TimelineItem());
     }
     std::optional<TimelineItem> predictions = mTokenManager.getPredictionsForToken(*token);
     if (predictions) {
-        return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
-                                                    std::move(debugName), PredictionState::Valid,
+        return std::make_unique<impl::SurfaceFrame>(*token, ownerPid, ownerUid,
+                                                    std::move(layerName), std::move(debugName),
+                                                    PredictionState::Valid,
                                                     std::move(*predictions));
     }
-    return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
+    return std::make_unique<impl::SurfaceFrame>(*token, ownerPid, ownerUid, std::move(layerName),
                                                 std::move(debugName), PredictionState::Expired,
                                                 TimelineItem());
 }
@@ -340,6 +423,7 @@
     ATRACE_CALL();
     const std::optional<TimelineItem> prediction = mTokenManager.getPredictionsForToken(token);
     std::lock_guard<std::mutex> lock(mMutex);
+    mCurrentDisplayFrame->token = token;
     if (!prediction) {
         mCurrentDisplayFrame->predictionState = PredictionState::Expired;
     } else {
@@ -408,6 +492,7 @@
             }
 
             totalJankReasons |= displayFrame->jankType;
+            traceDisplayFrame(*displayFrame);
 
             for (auto& surfaceFrame : displayFrame->surfaceFrames) {
                 if (surfaceFrame->getPresentState() == SurfaceFrame::PresentState::Presented) {
@@ -419,7 +504,6 @@
                     if (predictionState == PredictionState::Expired) {
                         // Jank analysis cannot be done on apps that don't use predictions
                         surfaceFrame->setJankInfo(TimeStats::JankType::PredictionExpired, 0);
-                        continue;
                     } else if (predictionState == PredictionState::Valid) {
                         const auto& actuals = surfaceFrame->getActuals();
                         const auto& predictions = surfaceFrame->getPredictions();
@@ -453,6 +537,7 @@
                         surfaceFrame->setJankInfo(jankType, jankMetadata);
                     }
                 }
+                surfaceFrame->traceSurfaceFrame(displayFrame->token);
             }
 
             mTimeStats->incrementJankyFrames(totalJankReasons);
@@ -569,4 +654,31 @@
     setMaxDisplayFrames(kDefaultMaxDisplayFrames);
 }
 
+void FrameTimeline::traceDisplayFrame(const DisplayFrame& displayFrame) {
+    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        if (displayFrame.token == ISurfaceComposer::INVALID_VSYNC_ID) {
+            ALOGD("Cannot trace DisplayFrame with invalid token");
+            return;
+        }
+        auto packet = ctx.NewTracePacket();
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        packet->set_timestamp(static_cast<uint64_t>(systemTime()));
+
+        auto* event = packet->set_frame_timeline_event();
+        auto* displayFrameEvent = event->set_display_frame();
+
+        displayFrameEvent->set_token(displayFrame.token);
+        displayFrameEvent->set_present_type(presentTypeToProto(displayFrame.jankMetadata));
+        displayFrameEvent->set_on_time_finish(!(displayFrame.jankMetadata & LateFinish));
+        displayFrameEvent->set_gpu_composition(displayFrame.jankMetadata & GpuComposition);
+        displayFrameEvent->set_jank_type(JankTypeToProto(displayFrame.jankType));
+
+        displayFrameEvent->set_expected_start_ns(displayFrame.surfaceFlingerPredictions.startTime);
+        displayFrameEvent->set_expected_end_ns(displayFrame.surfaceFlingerPredictions.endTime);
+
+        displayFrameEvent->set_actual_start_ns(displayFrame.surfaceFlingerActuals.startTime);
+        displayFrameEvent->set_actual_end_ns(displayFrame.surfaceFlingerActuals.endTime);
+    });
+}
+
 } // namespace android::frametimeline::impl
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index e61567e..9a74d50 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -18,6 +18,8 @@
 
 #include <../TimeStats/TimeStats.h>
 #include <gui/ISurfaceComposer.h>
+#include <perfetto/trace/android/frame_timeline_event.pbzero.h>
+#include <perfetto/tracing.h>
 #include <ui/FenceTime.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
@@ -128,6 +130,10 @@
     virtual ~FrameTimeline() = default;
     virtual TokenManager* getTokenManager() = 0;
 
+    // Initializes the Perfetto DataSource that emits DisplayFrame and SurfaceFrame events. Test
+    // classes can avoid double registration by mocking this function.
+    virtual void onBootFinished() = 0;
+
     // Create a new surface frame, set the predictions based on a token and return it to the caller.
     // Sets the PredictionState of SurfaceFrame.
     // Debug name is the human-readable debugging string for dumpsys.
@@ -191,8 +197,9 @@
 
 class SurfaceFrame : public android::frametimeline::SurfaceFrame {
 public:
-    SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
-                 PredictionState predictionState, TimelineItem&& predictions);
+    SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
+                 std::string debugName, PredictionState predictionState,
+                 TimelineItem&& predictions);
     ~SurfaceFrame() = default;
 
     TimelineItem getPredictions() const override { return mPredictions; };
@@ -202,6 +209,7 @@
     PredictionState getPredictionState() const override { return mPredictionState; };
     pid_t getOwnerPid() const override { return mOwnerPid; };
     TimeStats::JankType getJankType() const;
+    int64_t getToken() const { return mToken; };
     nsecs_t getBaseTime() const;
     uid_t getOwnerUid() const { return mOwnerUid; };
     const std::string& getName() const { return mLayerName; };
@@ -216,7 +224,13 @@
     // All the timestamps are dumped relative to the baseTime
     void dump(std::string& result, const std::string& indent, nsecs_t baseTime);
 
+    // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
+    // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
+    // DisplayFrame at the trace processor side.
+    void traceSurfaceFrame(int64_t displayFrameToken);
+
 private:
+    const int64_t mToken;
     const pid_t mOwnerPid;
     const uid_t mOwnerUid;
     const std::string mLayerName;
@@ -233,6 +247,12 @@
 
 class FrameTimeline : public android::frametimeline::FrameTimeline {
 public:
+    class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
+        void OnSetup(const SetupArgs&) override{};
+        void OnStart(const StartArgs&) override{};
+        void OnStop(const StopArgs&) override{};
+    };
+
     FrameTimeline(std::shared_ptr<TimeStats> timeStats);
     ~FrameTimeline() = default;
 
@@ -249,6 +269,14 @@
     void setMaxDisplayFrames(uint32_t size) override;
     void reset() override;
 
+    // Sets up the perfetto tracing backend and data source.
+    void onBootFinished() override;
+    // Registers the data source with the perfetto backend. Called as part of onBootFinished()
+    // and should not be called manually outside of tests.
+    void registerDataSource();
+
+    static constexpr char kFrameTimelineDataSource[] = "android.surfaceflinger.frametimeline";
+
 private:
     // Friend class for testing
     friend class android::frametimeline::FrameTimelineTest;
@@ -259,6 +287,8 @@
     struct DisplayFrame {
         DisplayFrame();
 
+        int64_t token = ISurfaceComposer::INVALID_VSYNC_ID;
+
         /* Usage of TimelineItem w.r.t SurfaceFlinger
          * startTime    Time when SurfaceFlinger wakes up to handle transactions and buffer updates
          * endTime      Time when SurfaceFlinger sends a composited frame to Display
@@ -270,9 +300,9 @@
         // Collection of predictions and actual values sent over by Layers
         std::vector<std::unique_ptr<SurfaceFrame>> surfaceFrames;
 
-        PredictionState predictionState;
+        PredictionState predictionState = PredictionState::None;
         TimeStats::JankType jankType = TimeStats::JankType::None; // Enum for the type of jank
-        int32_t jankMetadata = 0x0;         // Additional details about the jank
+        int32_t jankMetadata = 0x0; // Additional details about the jank
     };
 
     void flushPendingPresentFences() REQUIRES(mMutex);
@@ -285,6 +315,10 @@
     void dumpAll(std::string& result);
     void dumpJank(std::string& result);
 
+    // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
+    // enabled.
+    void traceDisplayFrame(const DisplayFrame& displayFrame) REQUIRES(mMutex);
+
     // Sliding window of display frames. TODO(b/168072834): compare perf with fixed size array
     std::deque<std::shared_ptr<DisplayFrame>> mDisplayFrames GUARDED_BY(mMutex);
     std::vector<std::pair<std::shared_ptr<FenceTime>, std::shared_ptr<DisplayFrame>>>
@@ -295,10 +329,10 @@
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
-    // The initial container size for the vector<SurfaceFrames> inside display frame. Although this
-    // number doesn't represent any bounds on the number of surface frames that can go in a display
-    // frame, this is a good starting size for the vector so that we can avoid the internal vector
-    // resizing that happens with push_back.
+    // The initial container size for the vector<SurfaceFrames> inside display frame. Although
+    // this number doesn't represent any bounds on the number of surface frames that can go in a
+    // display frame, this is a good starting size for the vector so that we can avoid the
+    // internal vector resizing that happens with push_back.
     static constexpr uint32_t kNumSurfaceFramesInitial = 10;
     // The various thresholds for App and SF. If the actual timestamp falls within the threshold
     // compared to prediction, we don't treat it as a jank.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 576bd50..773b3ab 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -331,7 +331,7 @@
         mInterceptor(mFactory.createSurfaceInterceptor()),
         mTimeStats(std::make_shared<impl::TimeStats>()),
         mFrameTracer(mFactory.createFrameTracer()),
-        mFrameTimeline(std::make_unique<frametimeline::impl::FrameTimeline>(mTimeStats)),
+        mFrameTimeline(mFactory.createFrameTimeline(mTimeStats)),
         mEventQueue(mFactory.createMessageQueue()),
         mCompositionEngine(mFactory.createCompositionEngine()),
         mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
@@ -621,6 +621,7 @@
 
     mFrameTracer->initialize();
     mTimeStats->onBootFinished();
+    mFrameTimeline->onBootFinished();
 
     // wait patiently for the window manager death
     const String16 name("window");
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index a93f5f6..bc487ac 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -134,6 +134,12 @@
 std::unique_ptr<FrameTracer> DefaultFactory::createFrameTracer() {
     return std::make_unique<FrameTracer>();
 }
+
+std::unique_ptr<frametimeline::FrameTimeline> DefaultFactory::createFrameTimeline(
+        std::shared_ptr<TimeStats> timeStats) {
+    return std::make_unique<frametimeline::impl::FrameTimeline>(timeStats);
+}
+
 } // namespace android::surfaceflinger
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 90032d4..fc45fa9 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -55,6 +55,8 @@
     sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) override;
     sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) override;
     std::unique_ptr<FrameTracer> createFrameTracer() override;
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats) override;
 };
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 69e9c3c..deb1b52 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -45,6 +45,7 @@
 class StartPropertySetThread;
 class SurfaceFlinger;
 class SurfaceInterceptor;
+class TimeStats;
 
 struct DisplayDeviceCreationArgs;
 struct ISchedulerCallback;
@@ -60,6 +61,10 @@
 class RefreshRateConfigs;
 } // namespace scheduler
 
+namespace frametimeline {
+class FrameTimeline;
+} // namespace frametimeline
+
 namespace surfaceflinger {
 
 class NativeWindowSurface;
@@ -102,6 +107,8 @@
     virtual sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) = 0;
     virtual sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) = 0;
     virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
+    virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats) = 0;
 
 protected:
     ~Factory() = default;
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 7427e27..871222c 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -83,6 +83,7 @@
         "mock/DisplayHardware/MockDisplay.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
+        "mock/MockFrameTimeline.cpp",
         "mock/MockFrameTracer.cpp",
         "mock/MockMessageQueue.cpp",
         "mock/MockNativeWindowSurface.cpp",
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 03c6f70..cb617d6 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -22,10 +22,16 @@
 #include <FrameTimeline/FrameTimeline.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
+#include <perfetto/trace/trace.pb.h>
 #include <cinttypes>
 
 using namespace std::chrono_literals;
 using testing::Contains;
+using FrameTimelineEvent = perfetto::protos::FrameTimelineEvent;
+using ProtoDisplayFrame = perfetto::protos::FrameTimelineEvent_DisplayFrame;
+using ProtoSurfaceFrame = perfetto::protos::FrameTimelineEvent_SurfaceFrame;
+using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
+using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
 
 MATCHER_P(HasBit, bit, "") {
     return (arg & bit) != 0;
@@ -47,14 +53,56 @@
         ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
+    static void SetUpTestSuite() {
+        // Need to initialize tracing in process for testing, and only once per test suite.
+        perfetto::TracingInitArgs args;
+        args.backends = perfetto::kInProcessBackend;
+        perfetto::Tracing::Initialize(args);
+    }
+
     void SetUp() override {
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats);
+        mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
         maxTokenRetentionTime = mTokenManager->kMaxRetentionTime;
     }
 
+    // Each tracing session can be used for a single block of Start -> Stop.
+    static std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest() {
+        perfetto::TraceConfig cfg;
+        cfg.set_duration_ms(500);
+        cfg.add_buffers()->set_size_kb(1024);
+        auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+        ds_cfg->set_name(impl::FrameTimeline::kFrameTimelineDataSource);
+
+        auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+        tracingSession->Setup(cfg);
+        return tracingSession;
+    }
+
+    std::vector<perfetto::protos::TracePacket> readFrameTimelinePacketsBlocking(
+            perfetto::TracingSession* tracingSession) {
+        std::vector<char> raw_trace = tracingSession->ReadTraceBlocking();
+        perfetto::protos::Trace trace;
+        EXPECT_TRUE(trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())));
+
+        std::vector<perfetto::protos::TracePacket> packets;
+        for (const auto& packet : trace.packet()) {
+            if (!packet.has_frame_timeline_event()) {
+                continue;
+            }
+            packets.emplace_back(packet);
+        }
+        return packets;
+    }
+
+    void addEmptyDisplayFrame() {
+        auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+        mFrameTimeline->setSfPresent(2500, presentFence1);
+    }
+
     void flushTokens(nsecs_t flushTime) {
         std::lock_guard<std::mutex> lock(mTokenManager->mMutex);
         mTokenManager->flushTokens(flushTime);
@@ -413,4 +461,308 @@
                                  presentFence1);
 }
 
+/*
+ * Tracing Tests
+ *
+ * Trace packets are flushed all the way only when the next packet is traced.
+ * For example: trace<Display/Surface>Frame() will create a TracePacket but not flush it. Only when
+ * another TracePacket is created, the previous one is guaranteed to be flushed. The following tests
+ * will have additional empty frames created for this reason.
+ */
+TEST_F(FrameTimelineTest, tracing_noPacketsSentWithoutTraceStart) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+    auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+                                                                    sLayerNameOne, token1);
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(token1, 20);
+    mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+    mFrameTimeline->setSfPresent(25, presentFence1);
+    presentFence1->signalForTest(30);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(token2, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, tracing_sanityTest) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    tracingSession->StartBlocking();
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+    auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+                                                                    sLayerNameOne, token1);
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(token2, 20);
+    mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
+                                    SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->setSfPresent(25, presentFence1);
+    presentFence1->signalForTest(30);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(token2, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+    presentFence2->signalForTest(55);
+
+    // The SurfaceFrame packet from the first frame is emitted, but not flushed yet. Emitting a new
+    // packet will flush it. To emit a new packet, we'll need to call flushPendingPresentFences()
+    // again, which is done by setSfPresent().
+    addEmptyDisplayFrame();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // Display Frame 1 has two packets - DisplayFrame and a SurfaceFrame.
+    // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+    // flushed through traced, so this is not counted.
+    EXPECT_EQ(packets.size(), 2);
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_invalidTokenDoesNotEmitTracePacket) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    tracingSession->StartBlocking();
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(-1, 20);
+    mFrameTimeline->setSfPresent(25, presentFence1);
+    presentFence1->signalForTest(30);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(token1, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+    presentFence2->signalForTest(60);
+
+    addEmptyDisplayFrame();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // Display Frame 1 has no packets.
+    // Display Frame 2 has one packet - DisplayFrame. However, this packet has
+    // been emitted but not flushed through traced, so this is not counted.
+    EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_invalidTokenDoesNotEmitTracePacket) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    tracingSession->StartBlocking();
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+    auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+                                                                    sLayerNameOne, std::nullopt);
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(token1, 20);
+    mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+    mFrameTimeline->setSfPresent(25, presentFence1);
+    presentFence1->signalForTest(30);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(token2, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+    presentFence2->signalForTest(60);
+
+    addEmptyDisplayFrame();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // Display Frame 1 has one packet - DisplayFrame (SurfaceFrame shouldn't be traced since it has
+    // an invalid token).
+    // Display Frame 2 has one packet - DisplayFrame. However, this packet has
+    // been emitted but not flushed through traced, so this is not counted.
+    EXPECT_EQ(packets.size(), 1);
+}
+
+void validateDisplayFrameEvent(const ProtoDisplayFrame& received, const ProtoDisplayFrame& source) {
+    ASSERT_TRUE(received.has_token());
+    EXPECT_EQ(received.token(), source.token());
+
+    ASSERT_TRUE(received.has_present_type());
+    EXPECT_EQ(received.present_type(), source.present_type());
+    ASSERT_TRUE(received.has_on_time_finish());
+    EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+    ASSERT_TRUE(received.has_gpu_composition());
+    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_expected_start_ns());
+    EXPECT_EQ(received.expected_start_ns(), source.expected_start_ns());
+    ASSERT_TRUE(received.has_expected_end_ns());
+    EXPECT_EQ(received.expected_end_ns(), source.expected_end_ns());
+
+    ASSERT_TRUE(received.has_actual_start_ns());
+    EXPECT_EQ(received.actual_start_ns(), source.actual_start_ns());
+    ASSERT_TRUE(received.has_actual_end_ns());
+    EXPECT_EQ(received.actual_end_ns(), source.actual_end_ns());
+}
+
+void validateSurfaceFrameEvent(const ProtoSurfaceFrame& received, const ProtoSurfaceFrame& source) {
+    ASSERT_TRUE(received.has_token());
+    EXPECT_EQ(received.token(), source.token());
+
+    ASSERT_TRUE(received.has_display_frame_token());
+    EXPECT_EQ(received.display_frame_token(), source.display_frame_token());
+
+    ASSERT_TRUE(received.has_present_type());
+    EXPECT_EQ(received.present_type(), source.present_type());
+    ASSERT_TRUE(received.has_on_time_finish());
+    EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+    ASSERT_TRUE(received.has_gpu_composition());
+    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_expected_start_ns());
+    EXPECT_EQ(received.expected_start_ns(), source.expected_start_ns());
+    ASSERT_TRUE(received.has_expected_end_ns());
+    EXPECT_EQ(received.expected_end_ns(), source.expected_end_ns());
+
+    ASSERT_TRUE(received.has_actual_start_ns());
+    EXPECT_EQ(received.actual_start_ns(), source.actual_start_ns());
+    ASSERT_TRUE(received.has_actual_end_ns());
+    EXPECT_EQ(received.actual_end_ns(), source.actual_end_ns());
+
+    ASSERT_TRUE(received.has_layer_name());
+    EXPECT_EQ(received.layer_name(), source.layer_name());
+    ASSERT_TRUE(received.has_pid());
+    EXPECT_EQ(received.pid(), source.pid());
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    tracingSession->StartBlocking();
+    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
+    int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
+    mFrameTimeline->setSfPresent(26, presentFence1);
+    presentFence1->signalForTest(31);
+
+    ProtoDisplayFrame protoDisplayFrame;
+    protoDisplayFrame.set_token(displayFrameToken1);
+    protoDisplayFrame.set_present_type(ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+    protoDisplayFrame.set_on_time_finish(true);
+    protoDisplayFrame.set_gpu_composition(false);
+    protoDisplayFrame.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+    protoDisplayFrame.set_expected_start_ns(10);
+    protoDisplayFrame.set_expected_end_ns(25);
+    protoDisplayFrame.set_actual_start_ns(20);
+    protoDisplayFrame.set_actual_end_ns(26);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+    presentFence2->signalForTest(55);
+
+    addEmptyDisplayFrame();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // Display Frame 1 has one packet - DisplayFrame.
+    // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+    // flushed through traced, so this is not counted.
+    EXPECT_EQ(packets.size(), 1);
+
+    const auto& packet = packets[0];
+    ASSERT_TRUE(packet.has_timestamp());
+    ASSERT_TRUE(packet.has_frame_timeline_event());
+
+    const auto& event = packet.frame_timeline_event();
+    ASSERT_TRUE(event.has_display_frame());
+    ASSERT_FALSE(event.has_surface_frame());
+    const auto& displayFrameEvent = event.display_frame();
+    validateDisplayFrameEvent(displayFrameEvent, protoDisplayFrame);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
+    auto tracingSession = getTracingSessionForTest();
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    tracingSession->StartBlocking();
+    int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 25, 40});
+    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({30, 35, 40});
+    int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+                                                       sLayerNameOne, surfaceFrameToken);
+    surfaceFrame1->setActualStartTime(0);
+    surfaceFrame1->setActualQueueTime(15);
+    surfaceFrame1->setAcquireFenceTime(20);
+
+    ProtoSurfaceFrame protoSurfaceFrame;
+    protoSurfaceFrame.set_token(surfaceFrameToken);
+    protoSurfaceFrame.set_display_frame_token(displayFrameToken1);
+    protoSurfaceFrame.set_present_type(ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+    protoSurfaceFrame.set_on_time_finish(true);
+    protoSurfaceFrame.set_gpu_composition(false);
+    protoSurfaceFrame.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+    protoSurfaceFrame.set_expected_start_ns(10);
+    protoSurfaceFrame.set_expected_end_ns(25);
+    protoSurfaceFrame.set_actual_start_ns(0);
+    protoSurfaceFrame.set_actual_end_ns(20);
+    protoSurfaceFrame.set_layer_name(sLayerNameOne);
+    protoSurfaceFrame.set_pid(sPidOne);
+
+    // Set up the display frame
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
+    mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
+                                    SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->setSfPresent(26, presentFence1);
+    presentFence1->signalForTest(31);
+
+    // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+    // next frame
+    mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+    mFrameTimeline->setSfPresent(55, presentFence2);
+    presentFence2->signalForTest(55);
+
+    addEmptyDisplayFrame();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // Display Frame 1 has one packet - DisplayFrame and a SurfaceFrame.
+    // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+    // flushed through traced, so this is not counted.
+    EXPECT_EQ(packets.size(), 2);
+
+    const auto& packet = packets[1];
+    ASSERT_TRUE(packet.has_timestamp());
+    ASSERT_TRUE(packet.has_frame_timeline_event());
+
+    const auto& event = packet.frame_timeline_event();
+    ASSERT_TRUE(!event.has_display_frame());
+    ASSERT_TRUE(event.has_surface_frame());
+    const auto& surfaceFrameEvent = event.surface_frame();
+    validateSurfaceFrameEvent(surfaceFrameEvent, protoSurfaceFrame);
+}
+
 } // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8a0276a..c0de465 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -42,6 +42,7 @@
 #include "TestableScheduler.h"
 #include "mock/DisplayHardware/MockDisplay.h"
 #include "mock/MockDisplayIdGenerator.h"
+#include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 
 namespace android {
@@ -154,6 +155,11 @@
         return std::make_unique<mock::FrameTracer>();
     }
 
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats) override {
+        return std::make_unique<mock::FrameTimeline>(timeStats);
+    }
+
     using CreateBufferQueueFunction =
             std::function<void(sp<IGraphicBufferProducer>* /* outProducer */,
                                sp<IGraphicBufferConsumer>* /* outConsumer */,
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
new file mode 100644
index 0000000..f784df3
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.
+ */
+
+#include "mock/MockFrameTimeline.h"
+
+namespace android::mock {
+
+// Explicit default instantiation is recommended.
+FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
+      : android::frametimeline::impl::FrameTimeline(timeStats) {}
+FrameTimeline::~FrameTimeline() = default;
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
new file mode 100644
index 0000000..81c32fe
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "FrameTimeline/FrameTimeline.h"
+
+namespace android::mock {
+
+class FrameTimeline : public android::frametimeline::impl::FrameTimeline {
+    // No need to create mocks for SurfaceFrame and TokenManager yet. They are very small components
+    // and do not have external dependencies like perfetto.
+public:
+    FrameTimeline(std::shared_ptr<TimeStats> timeStats);
+    ~FrameTimeline();
+
+    MOCK_METHOD0(onBootFinished, void());
+    MOCK_METHOD2(addSurfaceFrame,
+                 void(std::unique_ptr<frametimeline::SurfaceFrame>,
+                      frametimeline::SurfaceFrame::PresentState));
+    MOCK_METHOD2(setSfWakeUp, void(int64_t, nsecs_t));
+    MOCK_METHOD2(setSfPresent, void(nsecs_t, const std::shared_ptr<FenceTime>&));
+};
+
+} // namespace android::mock
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index fa742c5..75228d6 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -35,7 +35,7 @@
         "libhidlbase",
         "liblog",
         "libutils",
-        "android.hardware.vibrator-cpp",
+        "android.hardware.vibrator-unstable-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index e8606ca..bcd9957 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -46,7 +46,7 @@
 
 // -------------------------------------------------------------------------------------------------
 
-std::shared_ptr<HalWrapper> HalConnector::connect(std::shared_ptr<CallbackScheduler> scheduler) {
+std::shared_ptr<HalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
     static bool gHalExists = true;
     if (!gHalExists) {
         // We already tried to connect to all of the vibrator HAL versions and none was available.
@@ -106,7 +106,7 @@
         std::lock_guard<std::mutex> lock(mConnectedHalMutex);
         if (mConnectedHal == nullptr) {
             // Init was never called, so connect to HAL for the first time during this call.
-            mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+            mConnectedHal = mConnector(mCallbackScheduler);
 
             if (mConnectedHal == nullptr) {
                 ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
@@ -129,7 +129,7 @@
 bool HalController::init() {
     std::lock_guard<std::mutex> lock(mConnectedHalMutex);
     if (mConnectedHal == nullptr) {
-        mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+        mConnectedHal = mConnector(mCallbackScheduler);
     }
     return mConnectedHal != nullptr;
 }
@@ -142,7 +142,7 @@
 void HalController::tryReconnect() {
     std::lock_guard<std::mutex> lock(mConnectedHalMutex);
     if (mConnectedHal == nullptr) {
-        mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+        mConnectedHal = mConnector(mCallbackScheduler);
     } else {
         mConnectedHal->tryReconnect();
     }
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 9672644..7fee82f 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "VibratorHalWrapper"
 
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
 #include <android/hardware/vibrator/IVibrator.h>
 #include <hardware/vibrator.h>
 
@@ -73,17 +72,6 @@
         "android::hardware::vibrator::V1_0::Status = ";
 
 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();
-    }
-    if (status.isOk()) {
-        return HalResult<T>::ok(data);
-    }
-    return HalResult<T>::failed(std::string(status.toString8().c_str()));
-}
-
-template <typename T>
 HalResult<T> HalResult<T>::fromStatus(V1_0::Status status, T data) {
     switch (status) {
         case V1_0::Status::OK:
@@ -145,28 +133,16 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class HalCallbackWrapper : public Aidl::BnVibratorCallback {
-public:
-    HalCallbackWrapper(std::function<void()> completionCallback)
-          : mCompletionCallback(completionCallback) {}
-
-    binder::Status onComplete() override {
-        mCompletionCallback();
-        return binder::Status::ok();
-    }
-
-private:
-    const std::function<void()> mCompletionCallback;
-};
-
-// -------------------------------------------------------------------------------------------------
-
 HalResult<void> AidlHalWrapper::ping() {
     return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
 }
 
 void AidlHalWrapper::tryReconnect() {
-    sp<Aidl::IVibrator> newHandle = checkVintfService<Aidl::IVibrator>();
+    auto result = mReconnectFn();
+    if (!result.isOk()) {
+        return;
+    }
+    sp<Aidl::IVibrator> newHandle = result.value();
     if (newHandle) {
         std::lock_guard<std::mutex> lock(mHandleMutex);
         mHandle = std::move(newHandle);
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
new file mode 100644
index 0000000..b24e5c4
--- /dev/null
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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 "VibratorManagerHalController"
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalController.h>
+
+namespace Aidl = android::hardware::vibrator;
+
+namespace android {
+
+namespace vibrator {
+
+std::shared_ptr<ManagerHalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
+    static bool gHalExists = true;
+    if (gHalExists) {
+        sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
+        if (hal) {
+            ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
+            return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), aidlHal);
+        }
+    }
+
+    gHalExists = false;
+    return std::make_shared<LegacyManagerHalWrapper>();
+}
+
+static constexpr int MAX_RETRIES = 1;
+
+template <typename T>
+HalResult<T> ManagerHalController::processHalResult(HalResult<T> result, const char* functionName) {
+    if (result.isFailed()) {
+        ALOGE("%s failed: %s", functionName, result.errorMessage());
+        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+        mConnectedHal->tryReconnect();
+    }
+    return result;
+}
+
+template <typename T>
+HalResult<T> ManagerHalController::apply(ManagerHalController::hal_fn<T>& halFn,
+                                         const char* functionName) {
+    std::shared_ptr<ManagerHalWrapper> hal = nullptr;
+    {
+        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+        if (mConnectedHal == nullptr) {
+            // Init was never called, so connect to HAL for the first time during this call.
+            mConnectedHal = mConnector(mCallbackScheduler);
+
+            if (mConnectedHal == nullptr) {
+                ALOGV("Skipped %s because VibratorManager HAL is not available", functionName);
+                return HalResult<T>::unsupported();
+            }
+        }
+        hal = mConnectedHal;
+    }
+
+    HalResult<T> ret = processHalResult(halFn(hal), functionName);
+    for (int i = 0; i < MAX_RETRIES && ret.isFailed(); i++) {
+        ret = processHalResult(halFn(hal), functionName);
+    }
+
+    return ret;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool ManagerHalController::init() {
+    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+    if (mConnectedHal == nullptr) {
+        mConnectedHal = mConnector(mCallbackScheduler);
+    }
+    return mConnectedHal != nullptr;
+}
+
+HalResult<void> ManagerHalController::ping() {
+    hal_fn<void> pingFn = [](std::shared_ptr<ManagerHalWrapper> hal) { return hal->ping(); };
+    return apply(pingFn, "ping");
+}
+
+void ManagerHalController::tryReconnect() {
+    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+    if (mConnectedHal == nullptr) {
+        mConnectedHal = mConnector(mCallbackScheduler);
+    } else {
+        mConnectedHal->tryReconnect();
+    }
+}
+
+HalResult<ManagerCapabilities> ManagerHalController::getCapabilities() {
+    hal_fn<ManagerCapabilities> getCapabilitiesFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+        return hal->getCapabilities();
+    };
+    return apply(getCapabilitiesFn, "getCapabilities");
+}
+
+HalResult<std::vector<int32_t>> ManagerHalController::getVibratorIds() {
+    hal_fn<std::vector<int32_t>> getVibratorIdsFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+        return hal->getVibratorIds();
+    };
+    return apply(getVibratorIdsFn, "getVibratorIds");
+}
+
+HalResult<std::shared_ptr<HalController>> ManagerHalController::getVibrator(int32_t id) {
+    hal_fn<std::shared_ptr<HalController>> getVibratorFn =
+            [&](std::shared_ptr<ManagerHalWrapper> hal) { return hal->getVibrator(id); };
+    return apply(getVibratorFn, "getVibrator");
+}
+
+HalResult<void> ManagerHalController::prepareSynced(const std::vector<int32_t>& ids) {
+    hal_fn<void> prepareSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
+        return hal->prepareSynced(ids);
+    };
+    return apply(prepareSyncedFn, "prepareSynced");
+}
+
+HalResult<void> ManagerHalController::triggerSynced(
+        const std::function<void()>& completionCallback) {
+    hal_fn<void> triggerSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
+        return hal->triggerSynced(completionCallback);
+    };
+    return apply(triggerSyncedFn, "triggerSynced");
+}
+
+HalResult<void> ManagerHalController::cancelSynced() {
+    hal_fn<void> cancelSyncedFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+        return hal->cancelSynced();
+    };
+    return apply(cancelSyncedFn, "cancelSynced");
+}
+
+}; // namespace vibrator
+
+}; // namespace android
diff --git a/services/vibratorservice/VibratorManagerHalWrapper.cpp b/services/vibratorservice/VibratorManagerHalWrapper.cpp
index 71955af..8a08e5b 100644
--- a/services/vibratorservice/VibratorManagerHalWrapper.cpp
+++ b/services/vibratorservice/VibratorManagerHalWrapper.cpp
@@ -20,11 +20,14 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
+namespace Aidl = android::hardware::vibrator;
+
 namespace android {
 
 namespace vibrator {
 
 constexpr int32_t SINGLE_VIBRATOR_ID = 0;
+const constexpr char* MISSING_VIBRATOR_MESSAGE_PREFIX = "No vibrator with id=";
 
 HalResult<void> LegacyManagerHalWrapper::ping() {
     return mController->ping();
@@ -34,6 +37,10 @@
     mController->tryReconnect();
 }
 
+HalResult<ManagerCapabilities> LegacyManagerHalWrapper::getCapabilities() {
+    return HalResult<ManagerCapabilities>::ok(ManagerCapabilities::NONE);
+}
+
 HalResult<std::vector<int32_t>> LegacyManagerHalWrapper::getVibratorIds() {
     if (mController->init()) {
         return HalResult<std::vector<int32_t>>::ok(std::vector<int32_t>(1, SINGLE_VIBRATOR_ID));
@@ -47,7 +54,7 @@
         return HalResult<std::shared_ptr<HalController>>::ok(mController);
     }
     // Controller.init did not connect to any vibrator HAL service, so the device has no vibrator.
-    return HalResult<std::shared_ptr<HalController>>::failed("No vibrator with id = " +
+    return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
                                                              std::to_string(id));
 }
 
@@ -63,6 +70,136 @@
     return HalResult<void>::unsupported();
 }
 
+// -------------------------------------------------------------------------------------------------
+
+std::shared_ptr<HalWrapper> AidlManagerHalWrapper::connectToVibrator(
+        int32_t vibratorId, std::shared_ptr<CallbackScheduler> callbackScheduler) {
+    std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=]() {
+        sp<Aidl::IVibrator> vibrator;
+        auto result = this->getHal()->getVibrator(vibratorId, &vibrator);
+        return HalResult<sp<Aidl::IVibrator>>::fromStatus(result, vibrator);
+    };
+    auto result = reconnectFn();
+    if (!result.isOk()) {
+        return nullptr;
+    }
+    auto vibrator = result.value();
+    if (!vibrator) {
+        return nullptr;
+    }
+    return std::move(std::make_unique<AidlHalWrapper>(std::move(callbackScheduler),
+                                                      std::move(vibrator), reconnectFn));
+}
+
+HalResult<void> AidlManagerHalWrapper::ping() {
+    return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+}
+
+void AidlManagerHalWrapper::tryReconnect() {
+    sp<Aidl::IVibratorManager> newHandle = checkVintfService<Aidl::IVibratorManager>();
+    if (newHandle) {
+        std::lock_guard<std::mutex> lock(mHandleMutex);
+        mHandle = std::move(newHandle);
+    }
+}
+
+HalResult<ManagerCapabilities> AidlManagerHalWrapper::getCapabilities() {
+    std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
+    if (mCapabilities.has_value()) {
+        // Return copy of cached value.
+        return HalResult<ManagerCapabilities>::ok(*mCapabilities);
+    }
+    int32_t cap = 0;
+    auto result = getHal()->getCapabilities(&cap);
+    auto ret = HalResult<ManagerCapabilities>::fromStatus(result,
+                                                          static_cast<ManagerCapabilities>(cap));
+    if (ret.isOk()) {
+        // Cache copy of returned value.
+        mCapabilities.emplace(ret.value());
+    }
+    return ret;
+}
+
+HalResult<std::vector<int32_t>> AidlManagerHalWrapper::getVibratorIds() {
+    std::lock_guard<std::mutex> lock(mVibratorsMutex);
+    if (mVibratorIds.has_value()) {
+        // Return copy of cached values.
+        return HalResult<std::vector<int32_t>>::ok(*mVibratorIds);
+    }
+    std::vector<int32_t> ids;
+    auto result = getHal()->getVibratorIds(&ids);
+    auto ret = HalResult<std::vector<int32_t>>::fromStatus(result, ids);
+    if (ret.isOk()) {
+        // Cache copy of returned value and the individual controllers.
+        mVibratorIds.emplace(ret.value());
+        for (auto& id : ids) {
+            HalController::Connector connector = [&, id](auto scheduler) {
+                return this->connectToVibrator(id, scheduler);
+            };
+            auto controller = std::make_unique<HalController>(mCallbackScheduler, connector);
+            mVibrators[id] = std::move(controller);
+        }
+    }
+    return ret;
+}
+
+HalResult<std::shared_ptr<HalController>> AidlManagerHalWrapper::getVibrator(int32_t id) {
+    // Make sure we cache vibrator ids and initialize the individual controllers.
+    getVibratorIds();
+    std::lock_guard<std::mutex> lock(mVibratorsMutex);
+    auto it = mVibrators.find(id);
+    if (it != mVibrators.end()) {
+        return HalResult<std::shared_ptr<HalController>>::ok(it->second);
+    }
+    return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
+                                                             std::to_string(id));
+}
+
+HalResult<void> AidlManagerHalWrapper::prepareSynced(const std::vector<int32_t>& ids) {
+    auto ret = HalResult<void>::fromStatus(getHal()->prepareSynced(ids));
+    if (ret.isOk()) {
+        // Force reload of all vibrator controllers that were prepared for a sync operation here.
+        // This will trigger calls to getVibrator(id) on each controller, so they can use the
+        // latest service provided by this manager.
+        std::lock_guard<std::mutex> lock(mVibratorsMutex);
+        for (auto& id : ids) {
+            auto it = mVibrators.find(id);
+            if (it != mVibrators.end()) {
+                it->second->tryReconnect();
+            }
+        }
+    }
+    return ret;
+}
+
+HalResult<void> AidlManagerHalWrapper::triggerSynced(
+        const std::function<void()>& completionCallback) {
+    HalResult<ManagerCapabilities> capabilities = getCapabilities();
+    bool supportsCallback = capabilities.isOk() &&
+            static_cast<int32_t>(capabilities.value() & ManagerCapabilities::TRIGGER_CALLBACK);
+    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    return HalResult<void>::fromStatus(getHal()->triggerSynced(cb));
+}
+
+HalResult<void> AidlManagerHalWrapper::cancelSynced() {
+    auto ret = HalResult<void>::fromStatus(getHal()->cancelSynced());
+    if (ret.isOk()) {
+        // Force reload of all vibrator controllers that were prepared for a sync operation before.
+        // This will trigger calls to getVibrator(id) on each controller, so they can use the
+        // latest service provided by this manager.
+        std::lock_guard<std::mutex> lock(mVibratorsMutex);
+        for (auto& entry : mVibrators) {
+            entry.second->tryReconnect();
+        }
+    }
+    return ret;
+}
+
+sp<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
+    std::lock_guard<std::mutex> lock(mHandleMutex);
+    return mHandle;
+}
+
 }; // namespace vibrator
 
 }; // namespace android
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index c1a03a1..d3130f4 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -23,7 +23,7 @@
         "liblog",
         "libutils",
         "libvibratorservice",
-        "android.hardware.vibrator-cpp",
+        "android.hardware.vibrator-unstable-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index d1028a4..c405545 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -27,27 +27,20 @@
 
 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);
-};
+std::shared_ptr<HalWrapper> connectHal(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.
+// This relies on a given Connector to connect to the underlying Vibrator HAL service and reconnects
+// 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)
+    using Connector =
+            std::function<std::shared_ptr<HalWrapper>(std::shared_ptr<CallbackScheduler>)>;
+
+    HalController() : HalController(std::make_shared<CallbackScheduler>(), &connectHal) {}
+    HalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
           : HalWrapper(std::move(callbackScheduler)),
-            mHalConnector(std::move(halConnector)),
+            mConnector(connector),
             mConnectedHal(nullptr) {}
     virtual ~HalController() = default;
 
@@ -89,7 +82,7 @@
             const std::function<void()>& completionCallback) final override;
 
 private:
-    std::unique_ptr<HalConnector> mHalConnector;
+    Connector mConnector;
     std::mutex mConnectedHalMutex;
     // Shared pointer to allow local copies to be used by different threads.
     std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index bcb735d..638b483 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -19,6 +19,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/hardware/vibrator/BnVibratorCallback.h>
 #include <android/hardware/vibrator/IVibrator.h>
 #include <binder/IServiceManager.h>
 
@@ -40,7 +41,15 @@
     }
     static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
 
-    static HalResult<T> fromStatus(binder::Status status, T data);
+    static HalResult<T> fromStatus(binder::Status status, T data) {
+        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+            return HalResult<T>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<T>::ok(data);
+        }
+        return HalResult<T>::failed(std::string(status.toString8().c_str()));
+    }
     static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data);
 
     template <typename R>
@@ -99,6 +108,20 @@
           : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
 };
 
+class HalCallbackWrapper : public hardware::vibrator::BnVibratorCallback {
+public:
+    HalCallbackWrapper(std::function<void()> completionCallback)
+          : mCompletionCallback(completionCallback) {}
+
+    binder::Status onComplete() override {
+        mCompletionCallback();
+        return binder::Status::ok();
+    }
+
+private:
+    const std::function<void()> mCompletionCallback;
+};
+
 // -------------------------------------------------------------------------------------------------
 
 // Vibrator HAL capabilities.
@@ -178,9 +201,16 @@
 // Wrapper for the AIDL Vibrator HAL.
 class AidlHalWrapper : public HalWrapper {
 public:
-    AidlHalWrapper(std::shared_ptr<CallbackScheduler> scheduler,
-                   sp<hardware::vibrator::IVibrator> handle)
-          : HalWrapper(std::move(scheduler)), mHandle(std::move(handle)) {}
+    AidlHalWrapper(
+            std::shared_ptr<CallbackScheduler> scheduler, sp<hardware::vibrator::IVibrator> handle,
+            std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> reconnectFn =
+                    []() {
+                        return HalResult<sp<hardware::vibrator::IVibrator>>::ok(
+                                checkVintfService<hardware::vibrator::IVibrator>());
+                    })
+          : HalWrapper(std::move(scheduler)),
+            mReconnectFn(reconnectFn),
+            mHandle(std::move(handle)) {}
     virtual ~AidlHalWrapper() = default;
 
     HalResult<void> ping() override final;
@@ -211,6 +241,7 @@
             const std::function<void()>& completionCallback) override final;
 
 private:
+    const std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> mReconnectFn;
     std::mutex mHandleMutex;
     std::mutex mCapabilitiesMutex;
     std::mutex mSupportedEffectsMutex;
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
new file mode 100644
index 0000000..cf82562
--- /dev/null
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -0,0 +1,85 @@
+/*
+ * 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_VIBRATOR_MANAGER_HAL_CONTROLLER_H
+#define ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
+
+#include <android/hardware/vibrator/IVibratorManager.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <unordered_map>
+
+namespace android {
+
+namespace vibrator {
+
+std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler);
+
+// Controller for VibratorManager HAL handle.
+class ManagerHalController : public ManagerHalWrapper {
+public:
+    using Connector =
+            std::function<std::shared_ptr<ManagerHalWrapper>(std::shared_ptr<CallbackScheduler>)>;
+
+    ManagerHalController()
+          : ManagerHalController(std::make_shared<CallbackScheduler>(), &connectManagerHal) {}
+    ManagerHalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
+          : mConnector(connector), mConnectedHal(nullptr) {}
+    virtual ~ManagerHalController() = default;
+
+    /* Connects to the HAL service, possibly waiting for the registered service to
+     * become available. This will automatically be called at the first API usage if it was not
+     * manually called beforehand. Calling this manually during the setup phase can avoid slowing
+     * the first API call later on. This will fallback to a legacy manager implementation if the
+     * service is not available.
+     */
+    virtual void init();
+
+    /* reloads HAL service instance without waiting. This relies on the HAL found by init()
+     * to rapidly reconnect to the specific HAL service, or defers to init() if it was never called.
+     */
+    void tryReconnect() override final;
+
+    HalResult<void> ping() override final;
+
+    HalResult<ManagerCapabilities> getCapabilities() override final;
+    HalResult<std::vector<int32_t>> getVibratorIds() override final;
+    HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
+
+    HalResult<void> prepareSynced(const std::vector<int32_t>& ids) override final;
+    HalResult<void> triggerSynced(const std::function<void()>& completionCallback) override final;
+    HalResult<void> cancelSynced() override final;
+
+private:
+    Connector mConnector;
+    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<ManagerHalWrapper>)>;
+
+    template <typename T>
+    HalResult<T> apply(hal_fn<T>& halFn, const char* functionName);
+};
+
+}; // namespace vibrator
+
+}; // namespace android
+
+#endif // ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
index 99947a5..563f55e 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
@@ -17,12 +17,47 @@
 #ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 #define ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 
+#include <android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
+#include <unordered_map>
 
 namespace android {
 
 namespace vibrator {
 
+// VibratorManager HAL capabilities.
+enum class ManagerCapabilities : int32_t {
+    NONE = 0,
+    SYNC = hardware::vibrator::IVibratorManager::CAP_SYNC,
+    PREPARE_ON = hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
+    PREPARE_PERFORM = hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
+    PREPARE_COMPOSE = hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
+    MIXED_TRIGGER_ON = hardware::vibrator::IVibratorManager::IVibratorManager::CAP_MIXED_TRIGGER_ON,
+    MIXED_TRIGGER_PERFORM = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
+    MIXED_TRIGGER_COMPOSE = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
+    TRIGGER_CALLBACK = hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
+};
+
+inline ManagerCapabilities operator|(ManagerCapabilities lhs, ManagerCapabilities rhs) {
+    using underlying = typename std::underlying_type<ManagerCapabilities>::type;
+    return static_cast<ManagerCapabilities>(static_cast<underlying>(lhs) |
+                                            static_cast<underlying>(rhs));
+}
+
+inline ManagerCapabilities& operator|=(ManagerCapabilities& lhs, ManagerCapabilities rhs) {
+    return lhs = lhs | rhs;
+}
+
+inline ManagerCapabilities operator&(ManagerCapabilities lhs, ManagerCapabilities rhs) {
+    using underlying = typename std::underlying_type<ManagerCapabilities>::type;
+    return static_cast<ManagerCapabilities>(static_cast<underlying>(lhs) &
+                                            static_cast<underlying>(rhs));
+}
+
+inline ManagerCapabilities& operator&=(ManagerCapabilities& lhs, ManagerCapabilities rhs) {
+    return lhs = lhs & rhs;
+}
+
 // Wrapper for VibratorManager HAL handlers.
 class ManagerHalWrapper {
 public:
@@ -36,6 +71,7 @@
      */
     virtual void tryReconnect() = 0;
 
+    virtual HalResult<ManagerCapabilities> getCapabilities() = 0;
     virtual HalResult<std::vector<int32_t>> getVibratorIds() = 0;
     virtual HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) = 0;
 
@@ -55,6 +91,7 @@
     HalResult<void> ping() override final;
     void tryReconnect() override final;
 
+    HalResult<ManagerCapabilities> getCapabilities() override final;
     HalResult<std::vector<int32_t>> getVibratorIds() override final;
     HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
 
@@ -66,6 +103,41 @@
     const std::shared_ptr<HalController> mController;
 };
 
+// Wrapper for the AIDL VibratorManager HAL.
+class AidlManagerHalWrapper : public ManagerHalWrapper {
+public:
+    explicit AidlManagerHalWrapper(std::shared_ptr<CallbackScheduler> callbackScheduler,
+                                   sp<hardware::vibrator::IVibratorManager> handle)
+          : mHandle(std::move(handle)), mCallbackScheduler(callbackScheduler) {}
+    virtual ~AidlManagerHalWrapper() = default;
+
+    HalResult<void> ping() override final;
+    void tryReconnect() override final;
+
+    HalResult<ManagerCapabilities> getCapabilities() override final;
+    HalResult<std::vector<int32_t>> getVibratorIds() override final;
+    HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
+
+    HalResult<void> prepareSynced(const std::vector<int32_t>& ids) override final;
+    HalResult<void> triggerSynced(const std::function<void()>& completionCallback) override final;
+    HalResult<void> cancelSynced() override final;
+
+private:
+    std::mutex mHandleMutex;
+    std::mutex mCapabilitiesMutex;
+    std::mutex mVibratorsMutex;
+    sp<hardware::vibrator::IVibratorManager> mHandle GUARDED_BY(mHandleMutex);
+    std::optional<ManagerCapabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
+    std::optional<std::vector<int32_t>> mVibratorIds GUARDED_BY(mVibratorsMutex);
+    std::unordered_map<int32_t, std::shared_ptr<HalController>> mVibrators
+            GUARDED_BY(mVibratorsMutex);
+    std::shared_ptr<CallbackScheduler> mCallbackScheduler;
+
+    sp<hardware::vibrator::IVibratorManager> getHal();
+    std::shared_ptr<HalWrapper> connectToVibrator(int32_t vibratorId,
+                                                  std::shared_ptr<CallbackScheduler> scheduler);
+};
+
 }; // namespace vibrator
 
 }; // namespace android
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 5fc6d45..6801f76 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -23,6 +23,7 @@
         "VibratorHalWrapperHidlV1_1Test.cpp",
         "VibratorHalWrapperHidlV1_2Test.cpp",
         "VibratorHalWrapperHidlV1_3Test.cpp",
+        "VibratorManagerHalWrapperAidlTest.cpp",
         "VibratorManagerHalWrapperLegacyTest.cpp",
     ],
     cflags: [
@@ -37,7 +38,7 @@
         "liblog",
         "libvibratorservice",
         "libutils",
-        "android.hardware.vibrator-cpp",
+        "android.hardware.vibrator-unstable-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index cda5e9a..2d9d0d6 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -79,38 +79,6 @@
     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 {
@@ -119,9 +87,12 @@
         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));
+        mController = std::make_unique<
+                vibrator::HalController>(std::move(callbackScheduler),
+                                         [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+                                             android_atomic_inc(&(this->mConnectCounter));
+                                             return this->mMockHal;
+                                         });
         ASSERT_NE(mController, nullptr);
     }
 
@@ -334,9 +305,11 @@
 }
 
 TEST_F(VibratorHalControllerTest, TestNoVibratorReturnsUnsupportedAndAttemptsToReconnect) {
-    auto failingHalConnector = std::make_unique<FailingHalConnector>(&mConnectCounter);
-    mController =
-            std::make_unique<vibrator::HalController>(std::move(failingHalConnector), nullptr);
+    mController = std::make_unique<
+            vibrator::HalController>(nullptr, [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+        android_atomic_inc(&(this->mConnectCounter));
+        return nullptr;
+    });
     ASSERT_EQ(0, mConnectCounter);
 
     ASSERT_FALSE(mController->init());
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
new file mode 100644
index 0000000..3b036ee
--- /dev/null
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -0,0 +1,217 @@
+/*
+ * 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 "VibratorManagerHalControllerTest"
+
+#include <cutils/atomic.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+#include "test_utils.h"
+
+using namespace android;
+using namespace testing;
+
+static constexpr int MAX_ATTEMPTS = 2;
+
+class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
+public:
+    MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+    MOCK_METHOD(vibrator::HalResult<int32_t>, getCapabilities, (), (override));
+    MOCK_METHOD(vibrator::HalResult<std::vector<int32_t>>, getVibratorIds, (), (override));
+    MOCK_METHOD(vibrator::HalResult<std::shared_ptr<vibrator::HalController>>, getVibrator,
+                (int32_t id), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, prepareSynced, (const std::vector<int32_t>& ids),
+                (override));
+    MOCK_METHOD(vibrator::HalResult<void>, triggerSynced,
+                (const std::function<void()>& completionCallback), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, cancelSynced, (), (override));
+};
+
+class VibratorManagerHalControllerTest : public Test {
+public:
+    void SetUp() override {
+        mConnectCounter = 0;
+        auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
+        mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
+        mController = std::make_unique<
+                vibrator::HalController>(std::move(callbackScheduler),
+                                         [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+                                             android_atomic_inc(&(this->mConnectCounter));
+                                             return this->mMockHal;
+                                         });
+        ASSERT_NE(mController, nullptr);
+    }
+
+protected:
+    int32_t mConnectCounter;
+    std::shared_ptr<MockManagerHalWrapper> mMockHal;
+    std::unique_ptr<vibrator::ManagerHalController> mController;
+
+    void setHalExpectations(int32_t cardinality, std::vector<int32_t> ids,
+                            vibrator::HalResult<void> voidResult,
+                            vibrator::HalResult<vibrator::ManagerCapabilities> capabilitiesResult,
+                            vibrator::HalResult<std::vector<int32_t>> idsResult,
+                            vibrator::HalResult<std::shared_ptr<HalController>> vibratorResult) {
+        EXPECT_CALL(*mMockHal.get(), ping())
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(voidResult));
+        EXPECT_CALL(*mMockHal.get(), getCapabilities())
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(capabilitiesResult));
+        EXPECT_CALL(*mMockHal.get(), getVibratorIds())
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(idsResult));
+        EXPECT_CALL(*mMockHal.get(), getVibrator(_))
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(vibratorResult));
+        EXPECT_CALL(*mMockHal.get(), prepareSynced(_))
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(voidResult));
+        EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(voidResult));
+        EXPECT_CALL(*mMockHal.get(), cancelSynced())
+                .Times(Exactly(cardinality))
+                .WillRepeatedly(Return(voidResult));
+
+        if (cardinality > 1) {
+            // One reconnection call after each failure.
+            EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(7 * cardinality));
+        }
+    }
+};
+
+TEST_F(VibratorManagerHalControllerTest, TestInit) {
+    ASSERT_TRUE(mController->init());
+    ASSERT_EQ(1, mConnectCounter);
+
+    // Noop when wrapper was already initialized.
+    ASSERT_TRUE(mController->init());
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestApiCallsAreForwardedToHal) {
+    std::vector<int32_t> ids;
+    ids.push_back(1);
+    ids.push_back(2);
+
+    setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::ok(),
+                       vibrator::HalResult<vibrator::ManagerCapabilities>::ok(
+                               vibrator::ManagerCapabilities::SYNC),
+                       vibrator::HalResult<std::vector<int32_t>>::ok(ids),
+                       vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::ok(nullptr));
+
+    ASSERT_TRUE(mController->ping().isOk());
+
+    auto getCapabilitiesResult = mController->getCapabilities();
+    ASSERT_TRUE(getCapabilitiesResult.isOk());
+    ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, getCapabilitiesResult.value());
+
+    auto getVibratorIdsResult = mController->getVibratorIds();
+    ASSERT_TRUE(getVibratorIdsResult.isOk());
+    ASSERT_EQ(ids, getVibratorIdsResult.value());
+
+    auto getVibratorResult = mController->getVibrator(1);
+    ASSERT_TRUE(getVibratorResult.isOk());
+    ASSERT_EQ(nullptr, getVibratorResult.value());
+
+    ASSERT_TRUE(mController->prepareSynced(ids).isOk());
+    ASSERT_TRUE(mController->triggerSynced([]() {}).isOk());
+    ASSERT_TRUE(mController->cancelSynced().isOk());
+
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
+    std::vector<int32_t> ids;
+    setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::unsupported(),
+                       vibrator::HalResult<vibrator::ManagerCapabilities>::unsupported(),
+                       vibrator::HalResult<std::vector<int32_t>>::unsupported(),
+                       vibrator::HalResult<
+                               std::shared_ptr<vibrator::HalController>>::unsupported());
+
+    ASSERT_EQ(0, mConnectCounter);
+
+    ASSERT_TRUE(mController->ping().isUnsupported());
+    ASSERT_TRUE(mController->getCapabilities().isUnsupported());
+    ASSERT_TRUE(mController->getVibratorIds().isUnsupported());
+    ASSERT_TRUE(mController->getVibrator(1).isUnsupported());
+    ASSERT_TRUE(mController->prepareSynced(ids).isUnsupported());
+    ASSERT_TRUE(mController->triggerSynced([]() {}).isUnsupported());
+    ASSERT_TRUE(mController->cancelSynced().isUnsupported());
+
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultResetsHalConnection) {
+    std::vector<int32_t> ids;
+    setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::failed("message"),
+                       vibrator::HalResult<vibrator::ManagerCapabilities>::failed("message"),
+                       vibrator::HalResult<std::vector<int32_t>>::failed("message"),
+                       vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::failed(
+                               "message"));
+
+    ASSERT_EQ(0, mConnectCounter);
+
+    ASSERT_TRUE(mController->ping().isFailed());
+    ASSERT_TRUE(mController->getCapabilities().isFailed());
+    ASSERT_TRUE(mController->getVibratorIds().isFailed());
+    ASSERT_TRUE(mController->getVibrator(1).isFailed());
+    ASSERT_TRUE(mController->prepareSynced(ids).isFailed());
+    ASSERT_TRUE(mController->triggerSynced([]() {}).isFailed());
+    ASSERT_TRUE(mController->cancelSynced().isFailed());
+
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultReturnsSuccessAfterRetries) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), ping())
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+        EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), ping())
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
+    }
+
+    ASSERT_EQ(0, mConnectCounter);
+    ASSERT_TRUE(mController->ping().isOk());
+    ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, 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);
+}
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
new file mode 100644
index 0000000..dd71a6a
--- /dev/null
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -0,0 +1,358 @@
+/*
+ * 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 "VibratorManagerHalWrapperAidlTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+#include "test_utils.h"
+
+using android::binder::Status;
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::CompositePrimitive;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+using android::hardware::vibrator::IVibrator;
+using android::hardware::vibrator::IVibratorCallback;
+using android::hardware::vibrator::IVibratorManager;
+
+using namespace android;
+using namespace testing;
+
+class MockBinder : public BBinder {
+public:
+    MOCK_METHOD(status_t, linkToDeath,
+                (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
+    MOCK_METHOD(status_t, unlinkToDeath,
+                (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
+                 wp<DeathRecipient>* outRecipient),
+                (override));
+    MOCK_METHOD(status_t, pingBinder, (), (override));
+};
+
+class MockIVibrator : public IVibrator {
+public:
+    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(Status, off, (), (override));
+    MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
+    MOCK_METHOD(Status, perform,
+                (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
+                (override));
+    MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
+    MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
+    MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
+    MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
+    MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
+    MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
+                (override));
+    MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
+    MOCK_METHOD(Status, compose,
+                (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
+    MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
+    MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class MockIVibratorManager : public IVibratorManager {
+public:
+    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(Status, getVibratorIds, (std::vector<int32_t> * ret), (override));
+    MOCK_METHOD(Status, getVibrator, (int32_t id, sp<IVibrator>* ret), (override));
+    MOCK_METHOD(Status, prepareSynced, (const std::vector<int32_t>& ids), (override));
+    MOCK_METHOD(Status, triggerSynced, (const sp<IVibratorCallback>& cb), (override));
+    MOCK_METHOD(Status, cancelSynced, (), (override));
+    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorManagerHalWrapperAidlTest : public Test {
+public:
+    void SetUp() override {
+        mMockBinder = new StrictMock<MockBinder>();
+        mMockVibrator = new StrictMock<MockIVibrator>();
+        mMockHal = new StrictMock<MockIVibratorManager>();
+        mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+        mWrapper = std::make_unique<vibrator::AidlManagerHalWrapper>(mMockScheduler, mMockHal);
+        ASSERT_NE(mWrapper, nullptr);
+    }
+
+protected:
+    std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
+    std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
+    sp<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
+    sp<StrictMock<MockIVibrator>> mMockVibrator = nullptr;
+    sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+static const std::vector<int32_t> kVibratorIds = {1, 2};
+static constexpr int kVibratorId = 1;
+
+ACTION(TriggerCallback) {
+    if (arg0 != nullptr) {
+        arg0->onComplete();
+    }
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestPing) {
+    EXPECT_CALL(*mMockHal.get(), onAsBinder())
+            .Times(Exactly(2))
+            .WillRepeatedly(Return(mMockBinder.get()));
+    EXPECT_CALL(*mMockBinder.get(), pingBinder())
+            .Times(Exactly(2))
+            .WillOnce(Return(android::OK))
+            .WillRepeatedly(Return(android::DEAD_OBJECT));
+
+    ASSERT_TRUE(mWrapper->ping().isOk());
+    ASSERT_TRUE(mWrapper->ping().isFailed());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesDoesNotCacheFailedResult) {
+    EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+            .Times(Exactly(3))
+            .WillOnce(
+                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+
+    ASSERT_TRUE(mWrapper->getCapabilities().isUnsupported());
+    ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesCachesResult) {
+    EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+
+    std::vector<std::thread> threads;
+    for (int i = 0; i < 10; i++) {
+        threads.push_back(std::thread([&]() {
+            auto result = mWrapper->getCapabilities();
+            ASSERT_TRUE(result.isOk());
+            ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+        }));
+    }
+    std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsDoesNotCacheFailedResult) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(3))
+            .WillOnce(
+                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    ASSERT_TRUE(mWrapper->getVibratorIds().isUnsupported());
+    ASSERT_TRUE(mWrapper->getVibratorIds().isFailed());
+
+    auto result = mWrapper->getVibratorIds();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(kVibratorIds, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsCachesResult) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    std::vector<std::thread> threads;
+    for (int i = 0; i < 10; i++) {
+        threads.push_back(std::thread([&]() {
+            auto result = mWrapper->getVibratorIds();
+            ASSERT_TRUE(result.isOk());
+            ASSERT_EQ(kVibratorIds, result.value());
+        }));
+    }
+    std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+    auto result = mWrapper->getVibratorIds();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(kVibratorIds, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithValidIdReturnsController) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+        EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+    }
+
+    auto result = mWrapper->getVibrator(kVibratorId);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_NE(nullptr, result.value().get());
+    ASSERT_TRUE(result.value().get()->init());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithInvalidIdFails) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    ASSERT_TRUE(mWrapper->getVibrator(0).isFailed());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorRecoversVibratorPointer) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
+            .Times(Exactly(3))
+            .WillOnce(DoAll(SetArgPointee<1>(nullptr),
+                            Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY))))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+    EXPECT_CALL(*mMockVibrator.get(), getCapabilities(_))
+            .Times(Exactly(3))
+            .WillOnce(
+                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+
+    // Get vibrator controller is successful even if first getVibrator.
+    auto result = mWrapper->getVibrator(kVibratorId);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_NE(nullptr, result.value().get());
+
+    auto vibrator = result.value();
+    // First getVibrator call fails.
+    ASSERT_FALSE(vibrator->init());
+    // First and second getCapabilities calls fail, reload IVibrator with getVibrator.
+    ASSERT_FALSE(vibrator->getCapabilities().isOk());
+    // Third call to getCapabilities worked after IVibrator reloaded.
+    ASSERT_TRUE(vibrator->getCapabilities().isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestPrepareSynced) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+    EXPECT_CALL(*mMockHal.get(), prepareSynced(Eq(kVibratorIds)))
+            .Times(Exactly(3))
+            .WillOnce(
+                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(Return(Status()));
+
+    ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
+    ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isUnsupported());
+    ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isFailed());
+    ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestTriggerSyncedWithCallbackSupport) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
+                                      Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
+                .Times(Exactly(3))
+                .WillOnce(Return(
+                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+                .WillRepeatedly(DoAll(TriggerCallback(), Return(Status())));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->triggerSynced(callback).isUnsupported());
+    ASSERT_TRUE(mWrapper->triggerSynced(callback).isFailed());
+    ASSERT_TRUE(mWrapper->triggerSynced(callback).isOk());
+    ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestTriggerSyncedWithoutCallbackSupport) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), triggerSynced(Eq(nullptr)))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(Status()));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->triggerSynced(callback).isOk());
+    ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSynced) {
+    EXPECT_CALL(*mMockHal.get(), cancelSynced())
+            .Times(Exactly(3))
+            .WillOnce(
+                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(Return(Status()));
+
+    ASSERT_TRUE(mWrapper->cancelSynced().isUnsupported());
+    ASSERT_TRUE(mWrapper->cancelSynced().isFailed());
+    ASSERT_TRUE(mWrapper->cancelSynced().isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSyncedReloadsAllControllers) {
+    EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+    EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+    EXPECT_CALL(*mMockHal.get(), cancelSynced()).Times(Exactly(1)).WillRepeatedly(Return(Status()));
+
+    ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
+    ASSERT_TRUE(mWrapper->cancelSynced().isOk());
+}
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
index d5520a1..6c2aabb 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
@@ -78,6 +78,12 @@
     mWrapper->tryReconnect();
 }
 
+TEST_F(VibratorManagerHalWrapperLegacyTest, TestGetCapabilities) {
+    auto result = mWrapper->getCapabilities();
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(vibrator::ManagerCapabilities::NONE, result.value());
+}
+
 TEST_F(VibratorManagerHalWrapperLegacyTest, TestGetVibratorIds) {
     std::vector<int32_t> expectedIds;
     expectedIds.push_back(0);