Merge "Fix vsyncId for long frames and double stuffed"
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/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index ed0d75b..fdb8aaf 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -156,9 +156,7 @@
         "Scheduler/EventThread.cpp",
         "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
-        "Scheduler/LayerHistoryV2.cpp",
         "Scheduler/LayerInfo.cpp",
-        "Scheduler/LayerInfoV2.cpp",
         "Scheduler/MessageQueue.cpp",
         "Scheduler/RefreshRateConfigs.cpp",
         "Scheduler/Scheduler.cpp",
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 359ee26..c0d00f3 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * 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.
@@ -35,21 +35,21 @@
 #include "LayerInfo.h"
 #include "SchedulerUtils.h"
 
-namespace android::scheduler::impl {
+namespace android::scheduler {
 
 namespace {
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
+    // Layers with an explicit vote are always kept active
     if (layer.getFrameRateForLayerTree().rate > 0) {
-        return layer.isVisible();
+        return true;
     }
+
     return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
 }
 
 bool traceEnabled() {
-    char value[PROPERTY_VALUE_MAX];
-    property_get("debug.sf.layer_history_trace", value, "0");
-    return atoi(value);
+    return property_get_bool("debug.sf.layer_history_trace", false);
 }
 
 bool useFrameRatePriority() {
@@ -58,30 +58,44 @@
     return atoi(value);
 }
 
-void trace(const wp<Layer>& weak, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfo& info, LayerHistory::LayerVoteType type,
+           int fps) {
     const auto layer = weak.promote();
     if (!layer) return;
 
-    const auto& name = layer->getName();
-    const auto tag = "LFPS " + name;
-    ATRACE_INT(tag.c_str(), fps);
-    ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+    const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+    };
+
+    traceType(LayerHistory::LayerVoteType::NoVote, 1);
+    traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+    traceType(LayerHistory::LayerVoteType::Min, 1);
+    traceType(LayerHistory::LayerVoteType::Max, 1);
+
+    ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
 }
 } // namespace
 
-LayerHistory::LayerHistory()
-      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistory::LayerHistory(const RefreshRateConfigs& refreshRateConfigs)
+      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+    LayerInfo::setTraceEnabled(mTraceEnabled);
+    LayerInfo::setRefreshRateConfigs(refreshRateConfigs);
+}
+
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate,
-                                 LayerVoteType /*type*/) {
-    auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
+void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
+                                 LayerVoteType type) {
+    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+    auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
     std::lock_guard lock(mLock);
     mLayerInfos.emplace_back(layer, std::move(info));
 }
 
 void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
-                          LayerUpdateType /*updateType*/) {
+                          LayerUpdateType updateType) {
     std::lock_guard lock(mLock);
 
     const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -89,7 +103,7 @@
     LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
 
     const auto& info = it->second;
-    info->setLastPresentTime(presentTime, now);
+    info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
 
     // Activate layer if inactive.
     if (const auto end = activeLayers().end(); it >= end) {
@@ -99,54 +113,42 @@
 }
 
 LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
-    ATRACE_CALL();
+    LayerHistory::Summary summary;
+
     std::lock_guard lock(mLock);
 
     partitionLayers(now);
 
-    LayerHistory::Summary summary;
-    for (const auto& [weakLayer, info] : activeLayers()) {
-        const bool recent = info->isRecentlyActive(now);
-        auto layer = weakLayer.promote();
-        // Only use the layer if the reference still exists.
-        if (layer || CC_UNLIKELY(mTraceEnabled)) {
-            const auto layerFocused =
-                    Layer::isLayerFocusedBasedOnPriority(layer->getFrameRateSelectionPriority());
-            // Check if frame rate was set on layer.
-            const auto frameRate = layer->getFrameRateForLayerTree();
-            if (frameRate.rate > 0.f) {
-                const auto voteType = [&]() {
-                    switch (frameRate.type) {
-                        case Layer::FrameRateCompatibility::Default:
-                            return LayerVoteType::ExplicitDefault;
-                        case Layer::FrameRateCompatibility::ExactOrMultiple:
-                            return LayerVoteType::ExplicitExactOrMultiple;
-                        case Layer::FrameRateCompatibility::NoVote:
-                            return LayerVoteType::NoVote;
-                    }
-                }();
-                summary.push_back(
-                        RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
-                                                             .vote = voteType,
-                                                             .desiredRefreshRate = frameRate.rate,
-                                                             .seamlessness = frameRate.seamlessness,
-                                                             .weight = 1.0f,
-                                                             .focused = layerFocused});
-            } else if (recent) {
-                summary.push_back(
-                        RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
-                                                             .vote = LayerVoteType::Heuristic,
-                                                             .desiredRefreshRate =
-                                                                     info->getRefreshRate(now),
-                                                             .seamlessness =
-                                                                     Seamlessness::OnlySeamless,
-                                                             .weight = 1.0f,
-                                                             .focused = layerFocused});
-            }
+    for (const auto& [layer, info] : activeLayers()) {
+        const auto strong = layer.promote();
+        if (!strong) {
+            continue;
+        }
 
-            if (CC_UNLIKELY(mTraceEnabled)) {
-                trace(weakLayer, round<int>(frameRate.rate));
-            }
+        const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
+        const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
+        ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
+              frameRateSelectionPriority, layerFocused ? "" : "not");
+
+        const auto vote = info->getRefreshRateVote(now);
+        // Skip NoVote layer as those don't have any requirements
+        if (vote.type == LayerHistory::LayerVoteType::NoVote) {
+            continue;
+        }
+
+        // Compute the layer's position on the screen
+        const Rect bounds = Rect(strong->getBounds());
+        const ui::Transform transform = strong->getTransform();
+        constexpr bool roundOutwards = true;
+        Rect transformed = transform.transform(bounds, roundOutwards);
+
+        const float layerArea = transformed.getWidth() * transformed.getHeight();
+        float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+        summary.push_back(
+                {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
+
+        if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
         }
     }
 
@@ -162,14 +164,33 @@
         auto& [weak, info] = mLayerInfos[i];
         if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
             i++;
+            // Set layer vote if set
+            const auto frameRate = layer->getFrameRateForLayerTree();
+            const auto voteType = [&]() {
+                switch (frameRate.type) {
+                    case Layer::FrameRateCompatibility::Default:
+                        return LayerVoteType::ExplicitDefault;
+                    case Layer::FrameRateCompatibility::ExactOrMultiple:
+                        return LayerVoteType::ExplicitExactOrMultiple;
+                    case Layer::FrameRateCompatibility::NoVote:
+                        return LayerVoteType::NoVote;
+                }
+            }();
+
+            if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+                const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
+                info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
+            } else {
+                info->resetLayerVote();
+            }
             continue;
         }
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(weak, 0);
+            trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
         }
 
-        info->clearHistory();
+        info->onLayerInactive(now);
         std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
     }
 
@@ -190,10 +211,8 @@
     std::lock_guard lock(mLock);
 
     for (const auto& [layer, info] : activeLayers()) {
-        info->clearHistory();
+        info->clearHistory(systemTime());
     }
-
-    mActiveLayersEnd = 0;
 }
 
 std::string LayerHistory::dump() const {
@@ -202,4 +221,4 @@
                               mActiveLayersEnd);
 }
 
-} // namespace android::scheduler::impl
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 128699b..507ccc6 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -36,25 +36,23 @@
 namespace scheduler {
 
 class LayerHistoryTest;
-class LayerHistoryTestV2;
 class LayerInfo;
-class LayerInfoV2;
 
 class LayerHistory {
 public:
     using LayerVoteType = RefreshRateConfigs::LayerVoteType;
 
-    virtual ~LayerHistory() = default;
+    LayerHistory(const RefreshRateConfigs&);
+    ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
-                               LayerVoteType type) = 0;
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate, LayerVoteType type);
 
     // Sets the display size. Client is responsible for synchronization.
-    virtual void setDisplayArea(uint32_t displayArea) = 0;
+    void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
 
     // Sets whether a config change is pending to be applied
-    virtual void setConfigChangePending(bool pending) = 0;
+    void setConfigChangePending(bool pending) { mConfigChangePending = pending; }
 
     // Represents which layer activity is recorded
     enum class LayerUpdateType {
@@ -64,45 +62,18 @@
     };
 
     // Marks the layer as active, and records the given state to its history.
-    virtual void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) = 0;
+    void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType);
 
     using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
-    virtual Summary summarize(nsecs_t now) = 0;
+    Summary summarize(nsecs_t now);
 
-    virtual void clear() = 0;
-    virtual std::string dump() const = 0;
-};
-
-namespace impl {
-// Records per-layer history of scheduling-related information (primarily present time),
-// heuristically categorizes layers as active or inactive, and summarizes stats about
-// active layers (primarily maximum refresh rate). See go/content-fps-detection-in-scheduler.
-class LayerHistory : public android::scheduler::LayerHistory {
-public:
-    LayerHistory();
-    virtual ~LayerHistory();
-
-    // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
-                       LayerVoteType type) override;
-
-    void setDisplayArea(uint32_t /*displayArea*/) override {}
-
-    void setConfigChangePending(bool /*pending*/) override {}
-
-    // Marks the layer as active, and records the given state to its history.
-    void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
-    // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
-    android::scheduler::LayerHistory::Summary summarize(nsecs_t now) override;
-
-    void clear() override;
-    std::string dump() const override;
+    void clear();
+    std::string dump() const;
 
 private:
-    friend class android::scheduler::LayerHistoryTest;
+    friend LayerHistoryTest;
     friend TestableScheduler;
 
     using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfo>>;
@@ -130,65 +101,6 @@
     LayerInfos mLayerInfos GUARDED_BY(mLock);
     size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
 
-    // Whether to emit systrace output and debug logs.
-    const bool mTraceEnabled;
-
-    // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
-    const bool mUseFrameRatePriority;
-};
-
-class LayerHistoryV2 : public android::scheduler::LayerHistory {
-public:
-    LayerHistoryV2(const scheduler::RefreshRateConfigs&);
-    virtual ~LayerHistoryV2();
-
-    // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
-                       LayerVoteType type) override;
-
-    // Sets the display size. Client is responsible for synchronization.
-    void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
-
-    void setConfigChangePending(bool pending) override { mConfigChangePending = pending; }
-
-    // Marks the layer as active, and records the given state to its history.
-    void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
-    // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
-    android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
-
-    void clear() override;
-    std::string dump() const override;
-
-private:
-    friend android::scheduler::LayerHistoryTestV2;
-    friend TestableScheduler;
-
-    using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfoV2>>;
-    using LayerInfos = std::vector<LayerPair>;
-
-    struct ActiveLayers {
-        LayerInfos& infos;
-        const size_t index;
-
-        auto begin() { return infos.begin(); }
-        auto end() { return begin() + static_cast<long>(index); }
-    };
-
-    ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
-
-    // Iterates over layers in a single pass, swapping pairs such that active layers precede
-    // inactive layers, and inactive layers precede expired layers. Removes expired layers by
-    // truncating after inactive layers.
-    void partitionLayers(nsecs_t now) REQUIRES(mLock);
-
-    mutable std::mutex mLock;
-
-    // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
-    // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
-    LayerInfos mLayerInfos GUARDED_BY(mLock);
-    size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
-
     uint32_t mDisplayArea = 0;
 
     // Whether to emit systrace output and debug logs.
@@ -201,6 +113,5 @@
     std::atomic<bool> mConfigChangePending = false;
 };
 
-} // namespace impl
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
deleted file mode 100644
index e919d1b..0000000
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerHistory.h"
-
-#include <android-base/stringprintf.h>
-#include <cutils/properties.h>
-#include <utils/Log.h>
-#include <utils/Timers.h>
-#include <utils/Trace.h>
-
-#include <algorithm>
-#include <cmath>
-#include <string>
-#include <utility>
-
-#include "../Layer.h"
-#include "LayerInfoV2.h"
-#include "SchedulerUtils.h"
-
-namespace android::scheduler::impl {
-
-namespace {
-
-bool isLayerActive(const Layer& layer, const LayerInfoV2& info, nsecs_t threshold) {
-    // Layers with an explicit vote are always kept active
-    if (layer.getFrameRateForLayerTree().rate > 0) {
-        return true;
-    }
-
-    return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
-}
-
-bool traceEnabled() {
-    return property_get_bool("debug.sf.layer_history_trace", false);
-}
-
-bool useFrameRatePriority() {
-    char value[PROPERTY_VALUE_MAX];
-    property_get("debug.sf.use_frame_rate_priority", value, "1");
-    return atoi(value);
-}
-
-void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
-           int fps) {
-    const auto layer = weak.promote();
-    if (!layer) return;
-
-    const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
-        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
-    };
-
-    traceType(LayerHistory::LayerVoteType::NoVote, 1);
-    traceType(LayerHistory::LayerVoteType::Heuristic, fps);
-    traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
-    traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
-    traceType(LayerHistory::LayerVoteType::Min, 1);
-    traceType(LayerHistory::LayerVoteType::Max, 1);
-
-    ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
-}
-} // namespace
-
-LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
-      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
-    LayerInfoV2::setTraceEnabled(mTraceEnabled);
-    LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
-}
-
-LayerHistoryV2::~LayerHistoryV2() = default;
-
-void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
-                                   LayerVoteType type) {
-    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
-    auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
-    std::lock_guard lock(mLock);
-    mLayerInfos.emplace_back(layer, std::move(info));
-}
-
-void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
-                            LayerUpdateType updateType) {
-    std::lock_guard lock(mLock);
-
-    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
-                                 [layer](const auto& pair) { return pair.first == layer; });
-    LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
-
-    const auto& info = it->second;
-    info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
-
-    // Activate layer if inactive.
-    if (const auto end = activeLayers().end(); it >= end) {
-        std::iter_swap(it, end);
-        mActiveLayersEnd++;
-    }
-}
-
-LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
-    LayerHistory::Summary summary;
-
-    std::lock_guard lock(mLock);
-
-    partitionLayers(now);
-
-    for (const auto& [layer, info] : activeLayers()) {
-        const auto strong = layer.promote();
-        if (!strong) {
-            continue;
-        }
-
-        const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
-        const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
-        ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
-              frameRateSelectionPriority, layerFocused ? "" : "not");
-
-        const auto vote = info->getRefreshRateVote(now);
-        // Skip NoVote layer as those don't have any requirements
-        if (vote.type == LayerHistory::LayerVoteType::NoVote) {
-            continue;
-        }
-
-        // Compute the layer's position on the screen
-        const Rect bounds = Rect(strong->getBounds());
-        const ui::Transform transform = strong->getTransform();
-        constexpr bool roundOutwards = true;
-        Rect transformed = transform.transform(bounds, roundOutwards);
-
-        const float layerArea = transformed.getWidth() * transformed.getHeight();
-        float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
-        summary.push_back(
-                {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
-
-        if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
-        }
-    }
-
-    return summary;
-}
-
-void LayerHistoryV2::partitionLayers(nsecs_t now) {
-    const nsecs_t threshold = getActiveLayerThreshold(now);
-
-    // Collect expired and inactive layers after active layers.
-    size_t i = 0;
-    while (i < mActiveLayersEnd) {
-        auto& [weak, info] = mLayerInfos[i];
-        if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
-            i++;
-            // Set layer vote if set
-            const auto frameRate = layer->getFrameRateForLayerTree();
-            const auto voteType = [&]() {
-                switch (frameRate.type) {
-                    case Layer::FrameRateCompatibility::Default:
-                        return LayerVoteType::ExplicitDefault;
-                    case Layer::FrameRateCompatibility::ExactOrMultiple:
-                        return LayerVoteType::ExplicitExactOrMultiple;
-                    case Layer::FrameRateCompatibility::NoVote:
-                        return LayerVoteType::NoVote;
-                }
-            }();
-
-            if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
-                const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
-                info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
-            } else {
-                info->resetLayerVote();
-            }
-            continue;
-        }
-
-        if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
-        }
-
-        info->onLayerInactive(now);
-        std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
-    }
-
-    // Collect expired layers after inactive layers.
-    size_t end = mLayerInfos.size();
-    while (i < end) {
-        if (mLayerInfos[i].first.promote()) {
-            i++;
-        } else {
-            std::swap(mLayerInfos[i], mLayerInfos[--end]);
-        }
-    }
-
-    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
-}
-
-void LayerHistoryV2::clear() {
-    std::lock_guard lock(mLock);
-
-    for (const auto& [layer, info] : activeLayers()) {
-        info->clearHistory(systemTime());
-    }
-}
-
-std::string LayerHistoryV2::dump() const {
-    std::lock_guard lock(mLock);
-    return base::StringPrintf("LayerHistoryV2{size=%zu, active=%zu}", mLayerInfos.size(),
-                              mActiveLayersEnd);
-}
-
-} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 6d9dd43..66ac98a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,36 +14,284 @@
  * limitations under the License.
  */
 
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include "LayerInfo.h"
 
 #include <algorithm>
 #include <utility>
 
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfo"
+
 namespace android::scheduler {
 
-LayerInfo::LayerInfo(float lowRefreshRate, float highRefreshRate)
-      : mLowRefreshRate(lowRefreshRate), mHighRefreshRate(highRefreshRate) {}
+const RefreshRateConfigs* LayerInfo::sRefreshRateConfigs = nullptr;
+bool LayerInfo::sTraceEnabled = false;
 
-void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+LayerInfo::LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+                     LayerHistory::LayerVoteType defaultVote)
+      : mName(name),
+        mHighRefreshRatePeriod(highRefreshRatePeriod),
+        mDefaultVote(defaultVote),
+        mLayerVote({defaultVote, 0.0f}),
+        mRefreshRateHistory(name) {}
+
+void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+                                   bool pendingConfigChange) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
-    // Buffers can come with a present time far in the future. That keeps them relevant.
     mLastUpdatedTime = std::max(lastPresentTime, now);
-    mPresentTimeHistory.insertPresentTime(mLastUpdatedTime);
+    switch (updateType) {
+        case LayerUpdateType::AnimationTX:
+            mLastAnimationTime = std::max(lastPresentTime, now);
+            break;
+        case LayerUpdateType::SetFrameRate:
+        case LayerUpdateType::Buffer:
+            FrameTimeData frameTime = {.presetTime = lastPresentTime,
+                                       .queueTime = mLastUpdatedTime,
+                                       .pendingConfigChange = pendingConfigChange};
+            mFrameTimes.push_back(frameTime);
+            if (mFrameTimes.size() > HISTORY_SIZE) {
+                mFrameTimes.pop_front();
+            }
+            break;
+    }
+}
 
-    if (mLastPresentTime == 0) {
-        // First frame
-        mLastPresentTime = lastPresentTime;
-        return;
+bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
+    return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
+                                          mFrameTimeValidSince.time_since_epoch())
+                                          .count();
+}
+
+bool LayerInfo::isFrequent(nsecs_t now) const {
+    // If we know nothing about this layer we consider it as frequent as it might be the start
+    // of an animation.
+    if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+        return true;
     }
 
-    const nsecs_t period = lastPresentTime - mLastPresentTime;
-    mLastPresentTime = lastPresentTime;
-    // Ignore time diff that are too high - those are stale values
-    if (period > MAX_ACTIVE_LAYER_PERIOD_NS.count()) return;
+    // Find the first active frame
+    auto it = mFrameTimes.begin();
+    for (; it != mFrameTimes.end(); ++it) {
+        if (it->queueTime >= getActiveLayerThreshold(now)) {
+            break;
+        }
+    }
 
-    const float fps = std::min(1e9f / period, mHighRefreshRate);
-    mRefreshRateHistory.insertRefreshRate(fps);
+    const auto numFrames = std::distance(it, mFrameTimes.end());
+    if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
+        return false;
+    }
+
+    // Layer is considered frequent if the average frame rate is higher than the threshold
+    const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
+    return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
+}
+
+bool LayerInfo::isAnimating(nsecs_t now) const {
+    return mLastAnimationTime >= getActiveLayerThreshold(now);
+}
+
+bool LayerInfo::hasEnoughDataForHeuristic() const {
+    // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
+    if (mFrameTimes.size() < 2) {
+        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+        return false;
+    }
+
+    if (!isFrameTimeValid(mFrameTimes.front())) {
+        ALOGV("stale frames still captured");
+        return false;
+    }
+
+    const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+    if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+              totalDuration / 1e9f);
+        return false;
+    }
+
+    return true;
+}
+
+std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
+    nsecs_t totalPresentTimeDeltas = 0;
+    nsecs_t totalQueueTimeDeltas = 0;
+    bool missingPresentTime = false;
+    int numFrames = 0;
+    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        // Ignore frames captured during a config change
+        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+            return std::nullopt;
+        }
+
+        totalQueueTimeDeltas +=
+                std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+        numFrames++;
+
+        if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
+            missingPresentTime = true;
+            // If there are no presentation timestamps and we haven't calculated
+            // one in the past then we can't calculate the refresh rate
+            if (mLastRefreshRate.reported == 0) {
+                return std::nullopt;
+            }
+            continue;
+        }
+
+        totalPresentTimeDeltas +=
+                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+    }
+
+    // Calculate the average frame time based on presentation timestamps. If those
+    // doesn't exist, we look at the time the buffer was queued only. We can do that only if
+    // we calculated a refresh rate based on presentation timestamps in the past. The reason
+    // we look at the queue time is to handle cases where hwui attaches presentation timestamps
+    // when implementing render ahead for specific refresh rates. When hwui no longer provides
+    // presentation timestamps we look at the queue time to see if the current refresh rate still
+    // matches the content.
+
+    const auto averageFrameTime =
+            static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
+            numFrames;
+    return static_cast<nsecs_t>(averageFrameTime);
+}
+
+std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+    static constexpr float MARGIN = 1.0f; // 1Hz
+    if (!hasEnoughDataForHeuristic()) {
+        ALOGV("Not enough data");
+        return std::nullopt;
+    }
+
+    const auto averageFrameTime = calculateAverageFrameTime();
+    if (averageFrameTime.has_value()) {
+        const auto refreshRate = 1e9f / *averageFrameTime;
+        const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+        if (refreshRateConsistent) {
+            const auto knownRefreshRate =
+                    sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
+
+            // To avoid oscillation, use the last calculated refresh rate if it is
+            // close enough
+            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+                mLastRefreshRate.reported != knownRefreshRate) {
+                mLastRefreshRate.calculated = refreshRate;
+                mLastRefreshRate.reported = knownRefreshRate;
+            }
+
+            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        } else {
+            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        }
+    }
+
+    return mLastRefreshRate.reported == 0 ? std::nullopt
+                                          : std::make_optional(mLastRefreshRate.reported);
+}
+
+LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
+    if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+        ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
+        return mLayerVote;
+    }
+
+    if (isAnimating(now)) {
+        ALOGV("%s is animating", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
+        return {LayerHistory::LayerVoteType::Max, 0};
+    }
+
+    if (!isFrequent(now)) {
+        ALOGV("%s is infrequent", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
+        return {LayerHistory::LayerVoteType::Min, 0};
+    }
+
+    // If the layer was previously tagged as animating or infrequent, we clear
+    // the history as it is likely the layer just changed its behavior
+    // and we should not look at stale data
+    if (mLastRefreshRate.animatingOrInfrequent) {
+        clearHistory(now);
+    }
+
+    auto refreshRate = calculateRefreshRateIfPossible(now);
+    if (refreshRate.has_value()) {
+        ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+    }
+
+    ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
+    return {LayerHistory::LayerVoteType::Max, 0};
+}
+
+const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+    if (mTraceTags.count(type) == 0) {
+        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+        mTraceTags.emplace(type, tag);
+    }
+
+    return mTraceTags.at(type).c_str();
+}
+
+LayerInfo::RefreshRateHistory::HeuristicTraceTagData
+LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
+    const std::string prefix = "LFPS ";
+    const std::string suffix = "Heuristic ";
+    return {.min = prefix + mName + suffix + "min",
+            .max = prefix + mName + suffix + "max",
+            .consistent = prefix + mName + suffix + "consistent",
+            .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfo::RefreshRateHistory::clear() {
+    mRefreshRates.clear();
+}
+
+bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+    mRefreshRates.push_back({refreshRate, now});
+    while (mRefreshRates.size() >= HISTORY_SIZE ||
+           now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+        mRefreshRates.pop_front();
+    }
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+    }
+
+    return isConsistent();
+}
+
+bool LayerInfo::RefreshRateHistory::isConsistent() const {
+    if (mRefreshRates.empty()) return true;
+
+    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+    }
+
+    return consistent;
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 820624b..e434670 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -21,6 +21,9 @@
 #include <chrono>
 #include <deque>
 
+#include "LayerHistory.h"
+#include "RefreshRateConfigs.h"
+#include "Scheduler/Seamlessness.h"
 #include "SchedulerUtils.h"
 
 namespace android {
@@ -41,129 +44,179 @@
 
 // Stores history of present times and refresh rates for a layer.
 class LayerInfo {
+    using LayerUpdateType = LayerHistory::LayerUpdateType;
+
     // Layer is considered frequent if the earliest value in the window of most recent present times
     // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
     // favor of a low refresh rate.
     static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
-    static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
-
-    /**
-     * Struct that keeps the information about the refresh rate for last
-     * HISTORY_SIZE frames. This is used to better determine the refresh rate
-     * for individual layers.
-     */
-    class RefreshRateHistory {
-    public:
-        explicit RefreshRateHistory(float highRefreshRate) : mHighRefreshRate(highRefreshRate) {}
-
-        void insertRefreshRate(float refreshRate) {
-            mElements.push_back(refreshRate);
-            if (mElements.size() > HISTORY_SIZE) {
-                mElements.pop_front();
-            }
-        }
-
-        float getRefreshRateAvg() const {
-            return mElements.empty() ? mHighRefreshRate : calculate_mean(mElements);
-        }
-
-        void clearHistory() { mElements.clear(); }
-
-    private:
-        const float mHighRefreshRate;
-
-        static constexpr size_t HISTORY_SIZE = 30;
-        std::deque<float> mElements;
-    };
-
-    /**
-     * Struct that keeps the information about the present time for last
-     * HISTORY_SIZE frames. This is used to better determine whether the given layer
-     * is still relevant and it's refresh rate should be considered.
-     */
-    class PresentTimeHistory {
-    public:
-        static constexpr size_t HISTORY_SIZE = 90;
-
-        void insertPresentTime(nsecs_t presentTime) {
-            mElements.push_back(presentTime);
-            if (mElements.size() > HISTORY_SIZE) {
-                mElements.pop_front();
-            }
-        }
-
-        // Returns whether the earliest present time is within the active threshold.
-        bool isRecentlyActive(nsecs_t now) const {
-            if (mElements.size() < 2) {
-                return false;
-            }
-
-            // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
-            if (mElements.size() < HISTORY_SIZE &&
-                mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
-                return false;
-            }
-
-            return mElements.back() >= getActiveLayerThreshold(now);
-        }
-
-        bool isFrequent(nsecs_t now) const {
-            // Assume layer is infrequent if too few present times have been recorded.
-            if (mElements.size() < FREQUENT_LAYER_WINDOW_SIZE) {
-                return false;
-            }
-
-            // Layer is frequent if the earliest value in the window of most recent present times is
-            // within threshold.
-            const auto it = mElements.end() - FREQUENT_LAYER_WINDOW_SIZE;
-            const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
-            return *it >= threshold;
-        }
-
-        void clearHistory() { mElements.clear(); }
-
-    private:
-        std::deque<nsecs_t> mElements;
-        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
-    };
+    static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
+            std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
 
     friend class LayerHistoryTest;
 
 public:
-    LayerInfo(float lowRefreshRate, float highRefreshRate);
+    // Holds information about the layer vote
+    struct LayerVote {
+        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+        float fps = 0.0f;
+        Seamlessness seamlessness = Seamlessness::Default;
+    };
+
+    static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
+    static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+        sRefreshRateConfigs = &refreshRateConfigs;
+    }
+
+    LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+              LayerHistory::LayerVoteType defaultVote);
 
     LayerInfo(const LayerInfo&) = delete;
     LayerInfo& operator=(const LayerInfo&) = delete;
 
-    // Records the last requested oresent time. It also stores information about when
+    // Records the last requested present time. It also stores information about when
     // the layer was last updated. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
-    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+                            bool pendingConfigChange);
 
-    bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
-    bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
+    // Sets an explicit layer vote. This usually comes directly from the application via
+    // ANativeWindow_setFrameRate API
+    void setLayerVote(LayerVote vote) { mLayerVote = vote; }
 
-    float getRefreshRate(nsecs_t now) const {
-        return isFrequent(now) ? mRefreshRateHistory.getRefreshRateAvg() : mLowRefreshRate;
-    }
+    // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
+    // This is used for layers that called to setLayerVote() and then removed the vote, so that the
+    // layer can go back to whatever vote it had before the app voted for it.
+    void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+
+    // Resets the layer vote to its default.
+    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+
+    LayerVote getRefreshRateVote(nsecs_t now);
 
     // Return the last updated time. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
     nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
 
-    void clearHistory() {
-        mRefreshRateHistory.clearHistory();
-        mPresentTimeHistory.clearHistory();
+    // Returns a C string for tracing a vote
+    const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
+    void onLayerInactive(nsecs_t now) {
+        // Mark mFrameTimeValidSince to now to ignore all previous frame times.
+        // We are not deleting the old frame to keep track of whether we should treat the first
+        // buffer as Max as we don't know anything about this layer or Min as this layer is
+        // posting infrequent updates.
+        const auto timePoint = std::chrono::nanoseconds(now);
+        mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
+        mLastRefreshRate = {};
+        mRefreshRateHistory.clear();
+    }
+
+    void clearHistory(nsecs_t now) {
+        onLayerInactive(now);
+        mFrameTimes.clear();
     }
 
 private:
-    const float mLowRefreshRate;
-    const float mHighRefreshRate;
+    // Used to store the layer timestamps
+    struct FrameTimeData {
+        nsecs_t presetTime; // desiredPresentTime, if provided
+        nsecs_t queueTime;  // buffer queue time
+        bool pendingConfigChange;
+    };
+
+    // Holds information about the calculated and reported refresh rate
+    struct RefreshRateHeuristicData {
+        // Rate calculated on the layer
+        float calculated = 0.0f;
+        // Last reported rate for LayerInfo::getRefreshRate()
+        float reported = 0.0f;
+        // Whether the last reported rate for LayerInfo::getRefreshRate()
+        // was due to animation or infrequent updates
+        bool animatingOrInfrequent = false;
+    };
+
+    // Class to store past calculated refresh rate and determine whether
+    // the refresh rate calculated is consistent with past values
+    class RefreshRateHistory {
+    public:
+        static constexpr auto HISTORY_SIZE = 90;
+        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+        RefreshRateHistory(const std::string& name) : mName(name) {}
+
+        // Clears History
+        void clear();
+
+        // Adds a new refresh rate and returns true if it is consistent
+        bool add(float refreshRate, nsecs_t now);
+
+    private:
+        friend class LayerHistoryTest;
+
+        // Holds the refresh rate when it was calculated
+        struct RefreshRateData {
+            float refreshRate = 0.0f;
+            nsecs_t timestamp = 0;
+
+            bool operator<(const RefreshRateData& other) const {
+                return refreshRate < other.refreshRate;
+            }
+        };
+
+        // Holds tracing strings
+        struct HeuristicTraceTagData {
+            std::string min;
+            std::string max;
+            std::string consistent;
+            std::string average;
+        };
+
+        bool isConsistent() const;
+        HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+        const std::string mName;
+        mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+        std::deque<RefreshRateData> mRefreshRates;
+        static constexpr float MARGIN_FPS = 1.0;
+    };
+
+    bool isFrequent(nsecs_t now) const;
+    bool isAnimating(nsecs_t now) const;
+    bool hasEnoughDataForHeuristic() const;
+    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+    std::optional<nsecs_t> calculateAverageFrameTime() const;
+    bool isFrameTimeValid(const FrameTimeData&) const;
+
+    const std::string mName;
+
+    // Used for sanitizing the heuristic data
+    const nsecs_t mHighRefreshRatePeriod;
+    LayerHistory::LayerVoteType mDefaultVote;
+
+    LayerVote mLayerVote;
 
     nsecs_t mLastUpdatedTime = 0;
-    nsecs_t mLastPresentTime = 0;
-    RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
-    PresentTimeHistory mPresentTimeHistory;
+
+    nsecs_t mLastAnimationTime = 0;
+
+    RefreshRateHeuristicData mLastRefreshRate;
+
+    std::deque<FrameTimeData> mFrameTimes;
+    std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
+            std::chrono::steady_clock::now();
+    static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+    RefreshRateHistory mRefreshRateHistory;
+
+    mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
+
+    // Shared for all LayerInfo instances
+    static const RefreshRateConfigs* sRefreshRateConfigs;
+    static bool sTraceEnabled;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
deleted file mode 100644
index 94e7e20..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * 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.
- */
-
-// #define LOG_NDEBUG 0
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerInfoV2.h"
-
-#include <algorithm>
-#include <utility>
-
-#include <cutils/compiler.h>
-#include <cutils/trace.h>
-
-#undef LOG_TAG
-#define LOG_TAG "LayerInfoV2"
-
-namespace android::scheduler {
-
-const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
-bool LayerInfoV2::sTraceEnabled = false;
-
-LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
-                         LayerHistory::LayerVoteType defaultVote)
-      : mName(name),
-        mHighRefreshRatePeriod(highRefreshRatePeriod),
-        mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, 0.0f}),
-        mRefreshRateHistory(name) {}
-
-void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
-                                     LayerUpdateType updateType, bool pendingConfigChange) {
-    lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
-
-    mLastUpdatedTime = std::max(lastPresentTime, now);
-    switch (updateType) {
-        case LayerUpdateType::AnimationTX:
-            mLastAnimationTime = std::max(lastPresentTime, now);
-            break;
-        case LayerUpdateType::SetFrameRate:
-        case LayerUpdateType::Buffer:
-            FrameTimeData frameTime = {.presetTime = lastPresentTime,
-                                       .queueTime = mLastUpdatedTime,
-                                       .pendingConfigChange = pendingConfigChange};
-            mFrameTimes.push_back(frameTime);
-            if (mFrameTimes.size() > HISTORY_SIZE) {
-                mFrameTimes.pop_front();
-            }
-            break;
-    }
-}
-
-bool LayerInfoV2::isFrameTimeValid(const FrameTimeData& frameTime) const {
-    return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
-                                          mFrameTimeValidSince.time_since_epoch())
-                                          .count();
-}
-
-bool LayerInfoV2::isFrequent(nsecs_t now) const {
-    // If we know nothing about this layer we consider it as frequent as it might be the start
-    // of an animation.
-    if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
-        return true;
-    }
-
-    // Find the first active frame
-    auto it = mFrameTimes.begin();
-    for (; it != mFrameTimes.end(); ++it) {
-        if (it->queueTime >= getActiveLayerThreshold(now)) {
-            break;
-        }
-    }
-
-    const auto numFrames = std::distance(it, mFrameTimes.end());
-    if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
-        return false;
-    }
-
-    // Layer is considered frequent if the average frame rate is higher than the threshold
-    const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
-    return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
-}
-
-bool LayerInfoV2::isAnimating(nsecs_t now) const {
-    return mLastAnimationTime >= getActiveLayerThreshold(now);
-}
-
-bool LayerInfoV2::hasEnoughDataForHeuristic() const {
-    // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
-    if (mFrameTimes.size() < 2) {
-        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
-        return false;
-    }
-
-    if (!isFrameTimeValid(mFrameTimes.front())) {
-        ALOGV("stale frames still captured");
-        return false;
-    }
-
-    const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
-    if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
-        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
-              totalDuration / 1e9f);
-        return false;
-    }
-
-    return true;
-}
-
-std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
-    nsecs_t totalPresentTimeDeltas = 0;
-    nsecs_t totalQueueTimeDeltas = 0;
-    bool missingPresentTime = false;
-    int numFrames = 0;
-    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
-        // Ignore frames captured during a config change
-        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
-            return std::nullopt;
-        }
-
-        totalQueueTimeDeltas +=
-                std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
-        numFrames++;
-
-        if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
-            missingPresentTime = true;
-            // If there are no presentation timestamps and we haven't calculated
-            // one in the past then we can't calculate the refresh rate
-            if (mLastRefreshRate.reported == 0) {
-                return std::nullopt;
-            }
-            continue;
-        }
-
-        totalPresentTimeDeltas +=
-                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
-    }
-
-    // Calculate the average frame time based on presentation timestamps. If those
-    // doesn't exist, we look at the time the buffer was queued only. We can do that only if
-    // we calculated a refresh rate based on presentation timestamps in the past. The reason
-    // we look at the queue time is to handle cases where hwui attaches presentation timestamps
-    // when implementing render ahead for specific refresh rates. When hwui no longer provides
-    // presentation timestamps we look at the queue time to see if the current refresh rate still
-    // matches the content.
-
-    const auto averageFrameTime =
-            static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
-            numFrames;
-    return static_cast<nsecs_t>(averageFrameTime);
-}
-
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
-    static constexpr float MARGIN = 1.0f; // 1Hz
-    if (!hasEnoughDataForHeuristic()) {
-        ALOGV("Not enough data");
-        return std::nullopt;
-    }
-
-    const auto averageFrameTime = calculateAverageFrameTime();
-    if (averageFrameTime.has_value()) {
-        const auto refreshRate = 1e9f / *averageFrameTime;
-        const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
-        if (refreshRateConsistent) {
-            const auto knownRefreshRate =
-                    sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
-
-            // To avoid oscillation, use the last calculated refresh rate if it is
-            // close enough
-            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
-                mLastRefreshRate.reported != knownRefreshRate) {
-                mLastRefreshRate.calculated = refreshRate;
-                mLastRefreshRate.reported = knownRefreshRate;
-            }
-
-            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
-        } else {
-            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
-        }
-    }
-
-    return mLastRefreshRate.reported == 0 ? std::nullopt
-                                          : std::make_optional(mLastRefreshRate.reported);
-}
-
-LayerInfoV2::LayerVote LayerInfoV2::getRefreshRateVote(nsecs_t now) {
-    if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
-        ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
-        return mLayerVote;
-    }
-
-    if (isAnimating(now)) {
-        ALOGV("%s is animating", mName.c_str());
-        mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Max, 0};
-    }
-
-    if (!isFrequent(now)) {
-        ALOGV("%s is infrequent", mName.c_str());
-        mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Min, 0};
-    }
-
-    // If the layer was previously tagged as animating or infrequent, we clear
-    // the history as it is likely the layer just changed its behavior
-    // and we should not look at stale data
-    if (mLastRefreshRate.animatingOrInfrequent) {
-        clearHistory(now);
-    }
-
-    auto refreshRate = calculateRefreshRateIfPossible(now);
-    if (refreshRate.has_value()) {
-        ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
-        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
-    }
-
-    ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, 0};
-}
-
-const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
-    if (mTraceTags.count(type) == 0) {
-        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
-        mTraceTags.emplace(type, tag);
-    }
-
-    return mTraceTags.at(type).c_str();
-}
-
-LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
-LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
-    const std::string prefix = "LFPS ";
-    const std::string suffix = "Heuristic ";
-    return {.min = prefix + mName + suffix + "min",
-            .max = prefix + mName + suffix + "max",
-            .consistent = prefix + mName + suffix + "consistent",
-            .average = prefix + mName + suffix + "average"};
-}
-
-void LayerInfoV2::RefreshRateHistory::clear() {
-    mRefreshRates.clear();
-}
-
-bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
-    mRefreshRates.push_back({refreshRate, now});
-    while (mRefreshRates.size() >= HISTORY_SIZE ||
-           now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
-        mRefreshRates.pop_front();
-    }
-
-    if (CC_UNLIKELY(sTraceEnabled)) {
-        if (!mHeuristicTraceTagData.has_value()) {
-            mHeuristicTraceTagData = makeHeuristicTraceTagData();
-        }
-
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
-    }
-
-    return isConsistent();
-}
-
-bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
-    if (mRefreshRates.empty()) return true;
-
-    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
-    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
-    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
-
-    if (CC_UNLIKELY(sTraceEnabled)) {
-        if (!mHeuristicTraceTagData.has_value()) {
-            mHeuristicTraceTagData = makeHeuristicTraceTagData();
-        }
-
-        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
-        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
-        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
-    }
-
-    return consistent;
-}
-
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
deleted file mode 100644
index f94f4ab..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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 <utils/Timers.h>
-
-#include <chrono>
-#include <deque>
-
-#include "LayerHistory.h"
-#include "RefreshRateConfigs.h"
-#include "Scheduler/Seamlessness.h"
-#include "SchedulerUtils.h"
-
-namespace android {
-
-class Layer;
-
-namespace scheduler {
-
-using namespace std::chrono_literals;
-
-// Maximum period between presents for a layer to be considered active.
-constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
-
-// Earliest present time for a layer to be considered active.
-constexpr nsecs_t getActiveLayerThreshold(nsecs_t now) {
-    return now - MAX_ACTIVE_LAYER_PERIOD_NS.count();
-}
-
-// Stores history of present times and refresh rates for a layer.
-class LayerInfoV2 {
-    using LayerUpdateType = LayerHistory::LayerUpdateType;
-
-    // Layer is considered frequent if the earliest value in the window of most recent present times
-    // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
-    // favor of a low refresh rate.
-    static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
-    static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
-    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
-            std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
-
-    friend class LayerHistoryTestV2;
-
-public:
-    // Holds information about the layer vote
-    struct LayerVote {
-        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        float fps = 0.0f;
-        Seamlessness seamlessness = Seamlessness::Default;
-    };
-
-    static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
-
-    static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
-        sRefreshRateConfigs = &refreshRateConfigs;
-    }
-
-    LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
-                LayerHistory::LayerVoteType defaultVote);
-
-    LayerInfoV2(const LayerInfo&) = delete;
-    LayerInfoV2& operator=(const LayerInfoV2&) = delete;
-
-    // Records the last requested present time. It also stores information about when
-    // the layer was last updated. If the present time is farther in the future than the
-    // updated time, the updated time is the present time.
-    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
-                            bool pendingConfigChange);
-
-    // Sets an explicit layer vote. This usually comes directly from the application via
-    // ANativeWindow_setFrameRate API
-    void setLayerVote(LayerVote vote) { mLayerVote = vote; }
-
-    // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
-    // This is used for layers that called to setLayerVote() and then removed the vote, so that the
-    // layer can go back to whatever vote it had before the app voted for it.
-    void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
-
-    // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
-
-    LayerVote getRefreshRateVote(nsecs_t now);
-
-    // Return the last updated time. If the present time is farther in the future than the
-    // updated time, the updated time is the present time.
-    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
-
-    // Returns a C string for tracing a vote
-    const char* getTraceTag(LayerHistory::LayerVoteType type) const;
-
-    void onLayerInactive(nsecs_t now) {
-        // Mark mFrameTimeValidSince to now to ignore all previous frame times.
-        // We are not deleting the old frame to keep track of whether we should treat the first
-        // buffer as Max as we don't know anything about this layer or Min as this layer is
-        // posting infrequent updates.
-        const auto timePoint = std::chrono::nanoseconds(now);
-        mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
-        mLastRefreshRate = {};
-        mRefreshRateHistory.clear();
-    }
-
-    void clearHistory(nsecs_t now) {
-        onLayerInactive(now);
-        mFrameTimes.clear();
-    }
-
-private:
-    // Used to store the layer timestamps
-    struct FrameTimeData {
-        nsecs_t presetTime; // desiredPresentTime, if provided
-        nsecs_t queueTime;  // buffer queue time
-        bool pendingConfigChange;
-    };
-
-    // Holds information about the calculated and reported refresh rate
-    struct RefreshRateHeuristicData {
-        // Rate calculated on the layer
-        float calculated = 0.0f;
-        // Last reported rate for LayerInfoV2::getRefreshRate()
-        float reported = 0.0f;
-        // Whether the last reported rate for LayerInfoV2::getRefreshRate()
-        // was due to animation or infrequent updates
-        bool animatingOrInfrequent = false;
-    };
-
-    // Class to store past calculated refresh rate and determine whether
-    // the refresh rate calculated is consistent with past values
-    class RefreshRateHistory {
-    public:
-        static constexpr auto HISTORY_SIZE = 90;
-        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
-
-        RefreshRateHistory(const std::string& name) : mName(name) {}
-
-        // Clears History
-        void clear();
-
-        // Adds a new refresh rate and returns true if it is consistent
-        bool add(float refreshRate, nsecs_t now);
-
-    private:
-        friend class LayerHistoryTestV2;
-
-        // Holds the refresh rate when it was calculated
-        struct RefreshRateData {
-            float refreshRate = 0.0f;
-            nsecs_t timestamp = 0;
-
-            bool operator<(const RefreshRateData& other) const {
-                return refreshRate < other.refreshRate;
-            }
-        };
-
-        // Holds tracing strings
-        struct HeuristicTraceTagData {
-            std::string min;
-            std::string max;
-            std::string consistent;
-            std::string average;
-        };
-
-        bool isConsistent() const;
-        HeuristicTraceTagData makeHeuristicTraceTagData() const;
-
-        const std::string mName;
-        mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
-        std::deque<RefreshRateData> mRefreshRates;
-        static constexpr float MARGIN_FPS = 1.0;
-    };
-
-    bool isFrequent(nsecs_t now) const;
-    bool isAnimating(nsecs_t now) const;
-    bool hasEnoughDataForHeuristic() const;
-    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
-    std::optional<nsecs_t> calculateAverageFrameTime() const;
-    bool isFrameTimeValid(const FrameTimeData&) const;
-
-    const std::string mName;
-
-    // Used for sanitizing the heuristic data
-    const nsecs_t mHighRefreshRatePeriod;
-    LayerHistory::LayerVoteType mDefaultVote;
-
-    LayerVote mLayerVote;
-
-    nsecs_t mLastUpdatedTime = 0;
-
-    nsecs_t mLastAnimationTime = 0;
-
-    RefreshRateHeuristicData mLastRefreshRate;
-
-    std::deque<FrameTimeData> mFrameTimes;
-    std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
-            std::chrono::steady_clock::now();
-    static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
-    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
-
-    RefreshRateHistory mRefreshRateHistory;
-
-    mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
-
-    // Shared for all LayerInfo instances
-    static const RefreshRateConfigs* sRefreshRateConfigs;
-    static bool sTraceEnabled;
-};
-
-} // namespace scheduler
-} // namespace android
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index b14e482..83fa20e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -69,60 +69,6 @@
                               primaryRange.max, appRequestRange.min, appRequestRange.max);
 }
 
-const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
-        const std::vector<LayerRequirement>& layers) const {
-    std::lock_guard lock(mLock);
-    int contentFramerate = 0;
-    int explicitContentFramerate = 0;
-    for (const auto& layer : layers) {
-        const auto desiredRefreshRateRound = round<int>(layer.desiredRefreshRate);
-        if (layer.vote == LayerVoteType::ExplicitDefault ||
-            layer.vote == LayerVoteType::ExplicitExactOrMultiple) {
-            if (desiredRefreshRateRound > explicitContentFramerate) {
-                explicitContentFramerate = desiredRefreshRateRound;
-            }
-        } else {
-            if (desiredRefreshRateRound > contentFramerate) {
-                contentFramerate = desiredRefreshRateRound;
-            }
-        }
-    }
-
-    if (explicitContentFramerate != 0) {
-        contentFramerate = explicitContentFramerate;
-    } else if (contentFramerate == 0) {
-        contentFramerate = round<int>(mMaxSupportedRefreshRate->getFps());
-    }
-    ATRACE_INT("ContentFPS", contentFramerate);
-
-    // Find the appropriate refresh rate with minimal error
-    auto iter = min_element(mPrimaryRefreshRates.cbegin(), mPrimaryRefreshRates.cend(),
-                            [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
-                                return std::abs(lhs->fps - contentFramerate) <
-                                        std::abs(rhs->fps - contentFramerate);
-                            });
-
-    // Some content aligns better on higher refresh rate. For example for 45fps we should choose
-    // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
-    // align well with both
-    const RefreshRate* bestSoFar = *iter;
-    constexpr float MARGIN = 0.05f;
-    float ratio = (*iter)->fps / contentFramerate;
-    if (std::abs(std::round(ratio) - ratio) > MARGIN) {
-        while (iter != mPrimaryRefreshRates.cend()) {
-            ratio = (*iter)->fps / contentFramerate;
-
-            if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
-                bestSoFar = *iter;
-                break;
-            }
-            ++iter;
-        }
-    }
-
-    return *bestSoFar;
-}
-
 std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
                                                                  nsecs_t displayPeriod) const {
     auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index e84091e..6e0c0d3 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -232,17 +232,14 @@
 
         bool operator==(const LayerRequirement& other) const {
             return name == other.name && vote == other.vote &&
-                    desiredRefreshRate == other.desiredRefreshRate && weight == other.weight &&
+                    desiredRefreshRate == other.desiredRefreshRate &&
+                    seamlessness == other.seamlessness && weight == other.weight &&
                     focused == other.focused;
         }
 
         bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
     };
 
-    // Returns the refresh rate that fits best to the given layers.
-    const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
-            EXCLUDES(mLock);
-
     // Global state describing signals that affect refresh rate choice.
     struct GlobalSignals {
         // Whether the user touched the screen recently. Used to apply touch boost.
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f6d712e..52bf483 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -89,9 +89,8 @@
                                                 timerSlack.count(), vsyncMoveThreshold.count());
 }
 
-const char* toContentDetectionString(bool useContentDetection, bool useContentDetectionV2) {
-    if (!useContentDetection) return "off";
-    return useContentDetectionV2 ? "V2" : "V1";
+const char* toContentDetectionString(bool useContentDetection) {
+    return useContentDetection ? "on" : "off";
 }
 
 } // namespace
@@ -119,14 +118,13 @@
 Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
       : Scheduler(configs, callback,
                   {.supportKernelTimer = sysprop::support_kernel_idle_timer(false),
-                   .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false),
-                   .useContentDetectionV2 =
-                           base::GetBoolProperty("debug.sf.use_content_detection_v2"s, true)}) {}
+                   .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
+}
 
 Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
                      Options options)
       : Scheduler(createVsyncSchedule(options.supportKernelTimer), configs, callback,
-                  createLayerHistory(configs, options.useContentDetectionV2), options) {
+                  createLayerHistory(configs), options) {
     using namespace sysprop;
 
     const int setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
@@ -195,14 +193,10 @@
 }
 
 std::unique_ptr<LayerHistory> Scheduler::createLayerHistory(
-        const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2) {
+        const scheduler::RefreshRateConfigs& configs) {
     if (!configs.canSwitch()) return nullptr;
 
-    if (useContentDetectionV2) {
-        return std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
-    }
-
-    return std::make_unique<scheduler::impl::LayerHistory>();
+    return std::make_unique<scheduler::LayerHistory>(configs);
 }
 
 std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
@@ -528,14 +522,6 @@
         // still need to be registered.
         mLayerHistory->registerLayer(layer, minFps, maxFps,
                                      scheduler::LayerHistory::LayerVoteType::Max);
-    } else if (!mOptions.useContentDetectionV2) {
-        // In V1 of content detection, all layers are registered as Heuristic (unless it's
-        // wallpaper).
-        const auto highFps =
-                layer->getWindowType() == InputWindowInfo::Type::WALLPAPER ? minFps : maxFps;
-
-        mLayerHistory->registerLayer(layer, minFps, highFps,
-                                     scheduler::LayerHistory::LayerVoteType::Heuristic);
     } else {
         if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
             // Running Wallpaper at Min is considered as part of content detection.
@@ -574,8 +560,6 @@
             return;
         }
         mFeatures.contentRequirements = summary;
-        mFeatures.contentDetectionV1 =
-                !summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
 
         scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
         newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
@@ -682,8 +666,7 @@
     StringAppendF(&result, "+  Touch timer: %s\n",
                   mTouchTimer ? mTouchTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Content detection: %s %s\n\n",
-                  toContentDetectionString(mOptions.useContentDetection,
-                                           mOptions.useContentDetectionV2),
+                  toContentDetectionString(mOptions.useContentDetection),
                   mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
 }
 
@@ -740,29 +723,6 @@
     const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
     const bool idle = mIdleTimer && mFeatures.idleTimer == TimerState::Expired;
 
-    if (!mOptions.useContentDetectionV2) {
-        // As long as touch is active we want to be in performance mode.
-        if (touchActive) {
-            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
-        }
-
-        // If timer has expired as it means there is no new content on the screen.
-        if (idle) {
-            if (consideredSignals) consideredSignals->idle = true;
-            return mRefreshRateConfigs.getMinRefreshRateByPolicy().getConfigId();
-        }
-
-        // If content detection is off we choose performance as we don't know the content fps.
-        if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
-            // NOTE: V1 always calls this, but this is not a default behavior for V2.
-            return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
-        }
-
-        // Content detection is on, find the appropriate refresh rate with minimal error
-        return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements)
-                .getConfigId();
-    }
-
     return mRefreshRateConfigs
             .getBestRefreshRate(mFeatures.contentRequirements, {.touch = touchActive, .idle = idle},
                                 consideredSignals)
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 76e8f57..f16e1f9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -183,8 +183,6 @@
         bool supportKernelTimer;
         // Whether to use content detection at all.
         bool useContentDetection;
-        // Whether to use improved content detection.
-        bool useContentDetectionV2;
     };
 
     struct VsyncSchedule {
@@ -201,8 +199,7 @@
               std::unique_ptr<LayerHistory>, Options);
 
     static VsyncSchedule createVsyncSchedule(bool supportKernelIdleTimer);
-    static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&,
-                                                            bool useContentDetectionV2);
+    static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&);
 
     // Create a connection on the given EventThread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread>);
@@ -270,7 +267,6 @@
     std::mutex mFeatureStateLock;
 
     struct {
-        ContentDetectionState contentDetectionV1 = ContentDetectionState::Off;
         TimerState idleTimer = TimerState::Reset;
         TouchState touch = TouchState::Inactive;
         TimerState displayPowerTimer = TimerState::Expired;
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 18f3745..7427e27 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -48,7 +48,6 @@
         "HWComposerTest.cpp",
         "OneShotTimerTest.cpp",
         "LayerHistoryTest.cpp",
-        "LayerHistoryTestV2.cpp",
         "LayerMetadataTest.cpp",
         "MessageQueueTest.cpp",
         "PromiseTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 0fbe8ec..fbb4637 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #undef LOG_TAG
 #define LOG_TAG "LayerHistoryTest"
 
+#include <Layer.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
@@ -39,32 +36,74 @@
 
 class LayerHistoryTest : public testing::Test {
 protected:
-    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::PresentTimeHistory::HISTORY_SIZE;
+    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
     static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::MAX_FREQUENT_LAYER_PERIOD_NS;
+    static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::FREQUENT_LAYER_WINDOW_SIZE;
+    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
+    static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+            LayerInfo::RefreshRateHistory::HISTORY_DURATION;
 
     static constexpr float LO_FPS = 30.f;
-    static constexpr nsecs_t LO_FPS_PERIOD = 33'333'333;
+    static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
 
     static constexpr float HI_FPS = 90.f;
-    static constexpr nsecs_t HI_FPS_PERIOD = 11'111'111;
+    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
 
     LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
     void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
 
-    impl::LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
-    const impl::LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
+    LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
+    const LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
 
     size_t layerCount() const { return mScheduler->layerHistorySize(); }
     size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
 
-    size_t frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
         const auto& infos = history().mLayerInfos;
-        return std::count_if(infos.begin(), infos.begin() + history().mActiveLayersEnd,
+        return std::count_if(infos.begin(),
+                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
                              [now](const auto& pair) { return pair.second->isFrequent(now); });
     }
 
+    auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mLayerInfos;
+        return std::count_if(infos.begin(),
+                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+                             [now](const auto& pair) { return pair.second->isAnimating(now); });
+    }
+
+    void setDefaultLayerVote(Layer* layer,
+                             LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+        for (auto& [weak, info] : history().mLayerInfos) {
+            if (auto strong = weak.promote(); strong && strong.get() == layer) {
+                info->setDefaultLayerVote(vote);
+                return;
+            }
+        }
+    }
+
     auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+    auto createLayer(std::string name) {
+        return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
+    }
+
+    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
+                               float desiredRefreshRate, int numFrames) {
+        const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+        LayerHistory::Summary summary;
+        for (int i = 0; i < numFrames; i++) {
+            history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+            time += framePeriod;
+
+            summary = history().summarize(time);
+        }
+
+        ASSERT_EQ(1, summary.size());
+        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+                << "Frame rate is " << frameRate;
+    }
 
     Hwc2::mock::Display mDisplay;
     RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
@@ -78,14 +117,10 @@
                                 HwcConfigIndexType(0)};
 
     mock::NoOpSchedulerCallback mSchedulerCallback;
-    static constexpr bool kUseContentDetectionV2 = false;
 
-    TestableScheduler* const mScheduler =
-            new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
+    TestableScheduler* const mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback);
 
     TestableSurfaceFlinger mFlinger;
-
-    const nsecs_t mTime = systemTime();
 };
 
 namespace {
@@ -93,83 +128,259 @@
 TEST_F(LayerHistoryTest, oneLayer) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    // no layers are returned if no layers are active.
-    ASSERT_TRUE(history().summarize(mTime).empty());
+    const nsecs_t time = systemTime();
+
+    // No layers returned if no layers are active.
+    EXPECT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    // no layers are returned if active layers have insufficient history.
+    // Max returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_TRUE(history().summarize(mTime).empty());
+        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
     }
 
-    // High FPS is returned once enough history has been recorded.
+    // Max is returned since we have enough history but there is no timestamp votes.
     for (int i = 0; i < 10; i++) {
-        history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_EQ(1, history().summarize(mTime).size());
-        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
     }
 }
 
+TEST_F(LayerHistoryTest, oneInvisibleLayer) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+
+    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+    auto summary = history().summarize(time);
+    ASSERT_EQ(1, history().summarize(time).size());
+    // Layer is still considered inactive so we expect to get Min
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+    summary = history().summarize(time);
+    EXPECT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+}
+
 TEST_F(LayerHistoryTest, explicitTimestamp) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    nsecs_t time = mTime;
+    nsecs_t time = systemTime();
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
     }
 
-    ASSERT_EQ(1, history().summarize(mTime).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, oneLayerNoVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMinVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMaxVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVote) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(
+                    Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryTest, multipleLayers) {
     auto layer1 = createLayer();
     auto layer2 = createLayer();
     auto layer3 = createLayer();
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer1, getFrameSelectionPriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer2, getFrameSelectionPriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer3, getFrameSelectionPriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-    nsecs_t time = mTime;
+
+    nsecs_t time = systemTime();
 
     EXPECT_EQ(3, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
+    LayerHistory::Summary summary;
+
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
@@ -177,26 +388,30 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
     // layer1 is still active but infrequent.
     history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
 
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer1 is no longer active.
     // layer2 is frequent and has low refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -210,26 +425,36 @@
 
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
     history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer1 expires.
     layer1.clear();
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -239,42 +464,290 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer2 expires.
     layer2.clear();
-    ASSERT_TRUE(history().summarize(time).empty());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.empty());
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
     // layer3 becomes active and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer3 expires.
     layer3.clear();
-    ASSERT_TRUE(history().summarize(time).empty());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.empty());
     EXPECT_EQ(0, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, inactiveLayers) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    // the very first updates makes the layer frequent
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+    EXPECT_EQ(1, layerCount());
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // advance the time for the previous frame to be inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+    // Now event if we post a quick few frame we should stay infrequent
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(0, frequentLayerCount(time));
+    }
+
+    // More quick frames will get us to frequent again
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    time += HI_FPS_PERIOD;
+
+    EXPECT_EQ(1, layerCount());
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
+    auto explicitVisiblelayer = createLayer();
+    auto explicitInvisiblelayer = createLayer();
+
+    EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(
+                    Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+    EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(
+                    Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(explicitInvisiblelayer.get(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+
+    EXPECT_EQ(2, layerCount());
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // another update with the same cadence keep in infrequent
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // an update as animation will immediately vote for Max
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+    for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+        recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    }
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60_30Hz) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+class LayerHistoryTestParameterized : public LayerHistoryTest,
+                                      public testing::WithParamInterface<std::chrono::nanoseconds> {
+};
+
+TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
+    std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+    auto heuristicLayer = createLayer("HeuristicLayer");
+
+    EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate()));
+
+    auto infrequentLayer = createLayer("InfrequentLayer");
+    EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate()));
+
+    const nsecs_t startTime = systemTime();
+
+    const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+    history().record(heuristicLayer.get(), startTime, startTime,
+                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(infrequentLayer.get(), startTime, startTime,
+                     LayerHistory::LayerUpdateType::Buffer);
+
+    nsecs_t time = startTime;
+    nsecs_t lastInfrequentUpdate = startTime;
+    const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+    int infrequentLayerUpdates = 0;
+    while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+        time += heuristicUpdateDelta.count();
+        history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+
+        if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+            ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
+                  totalInfrequentLayerUpdates);
+            lastInfrequentUpdate = time;
+            history().record(infrequentLayer.get(), time, time,
+                             LayerHistory::LayerUpdateType::Buffer);
+            infrequentLayerUpdates++;
+        }
+
+        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+            ASSERT_NE(0, history().summarize(time).size());
+            ASSERT_GE(2, history().summarize(time).size());
+
+            bool max = false;
+            bool min = false;
+            float heuristic = 0;
+            for (const auto& layer : history().summarize(time)) {
+                if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+                    heuristic = layer.desiredRefreshRate;
+                } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+                    max = true;
+                } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+                    min = true;
+                }
+            }
+
+            if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+                EXPECT_FLOAT_EQ(24.0f, heuristic);
+                EXPECT_FALSE(max);
+                if (history().summarize(time).size() == 2) {
+                    EXPECT_TRUE(min);
+                }
+            }
+        }
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestParameterized,
+                        ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
 } // namespace
 } // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
deleted file mode 100644
index 7bfec9a..0000000
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryTestV2"
-
-#include <Layer.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#include "Scheduler/LayerHistory.h"
-#include "Scheduler/LayerInfoV2.h"
-#include "TestableScheduler.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/MockLayer.h"
-#include "mock/MockSchedulerCallback.h"
-
-using testing::_;
-using testing::Return;
-
-namespace android::scheduler {
-
-class LayerHistoryTestV2 : public testing::Test {
-protected:
-    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
-    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
-    static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
-    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
-    static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
-            LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
-
-    static constexpr float LO_FPS = 30.f;
-    static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
-
-    static constexpr float HI_FPS = 90.f;
-    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
-
-    LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
-
-    void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
-
-    impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
-    const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
-
-    size_t layerCount() const { return mScheduler->layerHistorySize(); }
-    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
-
-    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
-        const auto& infos = history().mLayerInfos;
-        return std::count_if(infos.begin(),
-                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
-                             [now](const auto& pair) { return pair.second->isFrequent(now); });
-    }
-
-    auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
-        const auto& infos = history().mLayerInfos;
-        return std::count_if(infos.begin(),
-                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
-                             [now](const auto& pair) { return pair.second->isAnimating(now); });
-    }
-
-    void setDefaultLayerVote(Layer* layer,
-                             LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
-        for (auto& [weak, info] : history().mLayerInfos) {
-            if (auto strong = weak.promote(); strong && strong.get() == layer) {
-                info->setDefaultLayerVote(vote);
-                return;
-            }
-        }
-    }
-
-    auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
-    auto createLayer(std::string name) {
-        return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
-    }
-
-    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
-                               float desiredRefreshRate, int numFrames) {
-        const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
-        impl::LayerHistoryV2::Summary summary;
-        for (int i = 0; i < numFrames; i++) {
-            history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-            time += framePeriod;
-
-            summary = history().summarize(time);
-        }
-
-        ASSERT_EQ(1, summary.size());
-        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
-                << "Frame rate is " << frameRate;
-    }
-
-    Hwc2::mock::Display mDisplay;
-    RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
-                                         .setVsyncPeriod(int32_t(LO_FPS_PERIOD))
-                                         .setConfigGroup(0)
-                                         .build(),
-                                 HWC2::Display::Config::Builder(mDisplay, 1)
-                                         .setVsyncPeriod(int32_t(HI_FPS_PERIOD))
-                                         .setConfigGroup(0)
-                                         .build()},
-                                HwcConfigIndexType(0)};
-
-    mock::NoOpSchedulerCallback mSchedulerCallback;
-    static constexpr bool kUseContentDetectionV2 = true;
-
-    TestableScheduler* const mScheduler =
-            new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
-
-    TestableSurfaceFlinger mFlinger;
-};
-
-namespace {
-
-TEST_F(LayerHistoryTestV2, oneLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    const nsecs_t time = systemTime();
-
-    // No layers returned if no layers are active.
-    EXPECT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-
-    // Max returned if active layers have insufficient history.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-    }
-
-    // Max is returned since we have enough history but there is no timestamp votes.
-    for (int i = 0; i < 10; i++) {
-        history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-    }
-}
-
-TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-
-    history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
-    auto summary = history().summarize(time);
-    ASSERT_EQ(1, history().summarize(time).size());
-    // Layer is still considered inactive so we expect to get Min
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-
-    summary = history().summarize(time);
-    EXPECT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTestV2, explicitTimestamp) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerNoVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMinVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMaxVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(history().summarize(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitVote) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitExactVote) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, multipleLayers) {
-    auto layer1 = createLayer();
-    auto layer2 = createLayer();
-    auto layer3 = createLayer();
-
-    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(3, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    impl::LayerHistoryV2::Summary summary;
-
-    // layer1 is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-        summary = history().summarize(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // layer2 is frequent and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = history().summarize(time);
-    }
-
-    // layer1 is still active but infrequent.
-    history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
-    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer1 is no longer active.
-    // layer2 is frequent and has low refresh rate.
-    for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-        summary = history().summarize(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer2 still has low refresh rate.
-    // layer3 has high refresh rate but not enough history.
-    constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        if (i % RATIO == 0) {
-            history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        }
-
-        history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = history().summarize(time);
-    }
-
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer3 becomes recently active.
-    history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    summary = history().summarize(time);
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer1 expires.
-    layer1.clear();
-    summary = history().summarize(time);
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
-    EXPECT_EQ(2, layerCount());
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer2 still has low refresh rate.
-    // layer3 becomes inactive.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-        summary = history().summarize(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer2 expires.
-    layer2.clear();
-    summary = history().summarize(time);
-    EXPECT_TRUE(summary.empty());
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // layer3 becomes active and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
-        history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = history().summarize(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer3 expires.
-    layer3.clear();
-    summary = history().summarize(time);
-    EXPECT_TRUE(summary.empty());
-    EXPECT_EQ(0, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, inactiveLayers) {
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    // the very first updates makes the layer frequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-        EXPECT_EQ(1, layerCount());
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        EXPECT_EQ(1, frequentLayerCount(time));
-    }
-
-    // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    EXPECT_EQ(1, layerCount());
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // advance the time for the previous frame to be inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-
-    // Now event if we post a quick few frame we should stay infrequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-
-        EXPECT_EQ(1, layerCount());
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        EXPECT_EQ(0, frequentLayerCount(time));
-    }
-
-    // More quick frames will get us to frequent again
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    time += HI_FPS_PERIOD;
-
-    EXPECT_EQ(1, layerCount());
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, invisibleExplicitLayer) {
-    auto explicitVisiblelayer = createLayer();
-    auto explicitInvisiblelayer = createLayer();
-
-    EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    nsecs_t time = systemTime();
-
-    // Post a buffer to the layers to make them active
-    history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    history().record(explicitInvisiblelayer.get(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-
-    EXPECT_EQ(2, layerCount());
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, infrequentAnimatingLayer) {
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // layer is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-    }
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // another update with the same cadence keep in infrequent
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // an update as animation will immediately vote for Max
-    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(1, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-    for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
-        recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-    }
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-class LayerHistoryTestV2Parameterized
-      : public LayerHistoryTestV2,
-        public testing::WithParamInterface<std::chrono::nanoseconds> {};
-
-TEST_P(LayerHistoryTestV2Parameterized, HeuristicLayerWithInfrequentLayer) {
-    std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
-    auto heuristicLayer = createLayer("HeuristicLayer");
-
-    EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate()));
-
-    auto infrequentLayer = createLayer("InfrequentLayer");
-    EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate()));
-
-    const nsecs_t startTime = systemTime();
-
-    const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
-    history().record(heuristicLayer.get(), startTime, startTime,
-                     LayerHistory::LayerUpdateType::Buffer);
-    history().record(infrequentLayer.get(), startTime, startTime,
-                     LayerHistory::LayerUpdateType::Buffer);
-
-    nsecs_t time = startTime;
-    nsecs_t lastInfrequentUpdate = startTime;
-    const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
-    int infrequentLayerUpdates = 0;
-    while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
-        time += heuristicUpdateDelta.count();
-        history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
-        if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
-            ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
-                  totalInfrequentLayerUpdates);
-            lastInfrequentUpdate = time;
-            history().record(infrequentLayer.get(), time, time,
-                             LayerHistory::LayerUpdateType::Buffer);
-            infrequentLayerUpdates++;
-        }
-
-        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
-            ASSERT_NE(0, history().summarize(time).size());
-            ASSERT_GE(2, history().summarize(time).size());
-
-            bool max = false;
-            bool min = false;
-            float heuristic = 0;
-            for (const auto& layer : history().summarize(time)) {
-                if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
-                    heuristic = layer.desiredRefreshRate;
-                } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
-                    max = true;
-                } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
-                    min = true;
-                }
-            }
-
-            if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
-                EXPECT_FLOAT_EQ(24.0f, heuristic);
-                EXPECT_FALSE(max);
-                if (history().summarize(time).size() == 2) {
-                    EXPECT_TRUE(min);
-                }
-            }
-        }
-    }
-}
-
-INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestV2Parameterized,
-                        ::testing::Values(1s, 2s, 3s, 4s, 5s));
-
-} // namespace
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 338e015..f2b7191 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -304,63 +304,6 @@
     }
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-
-    const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
-        return {{"testLayer", LayerVoteType::Heuristic, refreshRate, Seamlessness::OnlySeamless,
-                 /*weight*/ 1.0f, /*focused*/ false}};
-    };
-
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-}
-
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
@@ -847,29 +790,6 @@
     }
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent_Explicit) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 60.0f;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 90.0f;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getRefreshRateForContent(layers));
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 90.0f;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getRefreshRateForContent(layers));
-}
-
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getBestRefreshRate_Explicit) {
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m60_90Device,
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 647689b..fb02a33 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -68,8 +68,7 @@
         }
     } mExpectDisableVsync{mSchedulerCallback};
 
-    static constexpr bool kUseContentDetectionV2 = false;
-    TestableScheduler mScheduler{mConfigs, mSchedulerCallback, kUseContentDetectionV2};
+    TestableScheduler mScheduler{mConfigs, mSchedulerCallback};
 
     Scheduler::ConnectionHandle mConnectionHandle;
     mock::EventThread* mEventThread;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1b6e9ed..2192977 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,21 +32,16 @@
 
 class TestableScheduler : public Scheduler {
 public:
-    TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
-                      bool useContentDetectionV2)
+    TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_unique<mock::VSyncTracker>(), configs, callback,
-                              useContentDetectionV2) {}
+                              std::make_unique<mock::VSyncTracker>(), configs, callback) {}
 
     TestableScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
                       std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
-                      const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
-                      bool useContentDetectionV2)
+                      const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
           : Scheduler({std::move(vsyncController), std::move(vsyncTracker), nullptr}, configs,
-                      callback, createLayerHistory(configs, useContentDetectionV2),
-                      {.supportKernelTimer = false,
-                       .useContentDetection = true,
-                       .useContentDetectionV2 = useContentDetectionV2}) {}
+                      callback, createLayerHistory(configs),
+                      {.supportKernelTimer = false, .useContentDetection = true}) {}
 
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -62,20 +57,11 @@
 
     bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
 
-    auto* mutableLayerHistory() {
-        LOG_ALWAYS_FATAL_IF(mOptions.useContentDetectionV2);
-        return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
-    }
-
-    auto* mutableLayerHistoryV2() {
-        LOG_ALWAYS_FATAL_IF(!mOptions.useContentDetectionV2);
-        return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
-    }
+    auto* mutableLayerHistory() { return mLayerHistory.get(); }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         if (!mLayerHistory) return 0;
-        return mOptions.useContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
-                                              : mutableLayerHistory()->mLayerInfos.size();
+        return mutableLayerHistory()->mLayerInfos.size();
     }
 
     void replaceTouchTimer(int64_t millis) {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index eba3f8e..8a0276a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -227,10 +227,8 @@
                 mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
         mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
 
-        constexpr bool kUseContentDetectionV2 = false;
         mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                                           *mFlinger->mRefreshRateConfigs, *(callback ?: this),
-                                           kUseContentDetectionV2);
+                                           *mFlinger->mRefreshRateConfigs, *(callback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
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);