Merge "Add Shared timeline jank classification listener (1/2)"
diff --git a/cmds/bugreport/bugreport.cpp b/cmds/bugreport/bugreport.cpp
index e5c52d8..f81994b 100644
--- a/cmds/bugreport/bugreport.cpp
+++ b/cmds/bugreport/bugreport.cpp
@@ -14,91 +14,19 @@
  * limitations under the License.
  */
 
-#include <errno.h>
 #include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
 
-#include <cutils/properties.h>
-#include <cutils/sockets.h>
+// Only prints a warning redirecting to bugreportz.
+int main() {
+    fprintf(stderr,
+            "=============================================================================\n");
+    fprintf(stderr, "WARNING: Flat (text file, non-zipped) bugreports are deprecated.\n");
+    fprintf(stderr, "WARNING: Please generate zipped bugreports instead.\n");
+    fprintf(stderr, "WARNING: On the host use: adb bugreport filename.zip\n");
+    fprintf(stderr, "WARNING: On the device use: bugreportz\n");
+    fprintf(stderr, "WARNING: bugreportz will output the filename to use with adb pull.\n");
+    fprintf(stderr,
+            "=============================================================================\n\n\n");
 
-// This program will trigger the dumpstate service to start a call to
-// dumpstate, then connect to the dumpstate local client to read the
-// output. All of the dumpstate output is written to stdout, including
-// any errors encountered while reading/writing the output.
-int main(int argc, char* /*argv*/[]) {
-  fprintf(stderr, "=============================================================================\n");
-  fprintf(stderr, "WARNING: Flat (text file, non-zipped) bugreports are deprecated.\n");
-  fprintf(stderr, "WARNING: Please generate zipped bugreports instead.\n");
-  fprintf(stderr, "WARNING: On the host use: adb bugreport filename.zip\n");
-  fprintf(stderr, "WARNING: On the device use: bugreportz\n");
-  fprintf(stderr, "WARNING: bugreportz will output the filename to use with adb pull.\n");
-  fprintf(stderr, "=============================================================================\n\n\n");
-
-  if (argc != 1) {
-    fprintf(stderr, "usage: bugreport\n");
-    exit(1);
-  }
-
-  // Start the dumpstate service.
-  property_set("ctl.start", "dumpstate");
-
-  // Socket will not be available until service starts.
-  int s = -1;
-  for (int i = 0; i < 20; i++) {
-    s = socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED,
-                            SOCK_STREAM);
-    if (s >= 0)
-      break;
-    // Try again in 1 second.
-    sleep(1);
-  }
-
-  if (s == -1) {
-    printf("Failed to connect to dumpstate service: %s\n", strerror(errno));
-    return 1;
-  }
-
-  // Set a timeout so that if nothing is read in 3 minutes, we'll stop
-  // reading and quit. No timeout in dumpstate is longer than 60 seconds,
-  // so this gives lots of leeway in case of unforeseen time outs.
-  struct timeval tv;
-  tv.tv_sec = 3 * 60;
-  tv.tv_usec = 0;
-  if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
-    printf("WARNING: Cannot set socket timeout: %s\n", strerror(errno));
-  }
-
-  while (1) {
-    char buffer[65536];
-    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)));
-    if (bytes_read == 0) {
-      break;
-    } else if (bytes_read == -1) {
-      // EAGAIN really means time out, so change the errno.
-      if (errno == EAGAIN) {
-        errno = ETIMEDOUT;
-      }
-      printf("\nBugreport read terminated abnormally (%s).\n", strerror(errno));
-      break;
-    }
-
-    ssize_t bytes_to_send = bytes_read;
-    ssize_t bytes_written;
-    do {
-      bytes_written = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
-                                               buffer + bytes_read - bytes_to_send,
-                                               bytes_to_send));
-      if (bytes_written == -1) {
-        printf("Failed to write data to stdout: read %zd, trying to send %zd (%s)\n",
-               bytes_read, bytes_to_send, strerror(errno));
-        return 1;
-      }
-      bytes_to_send -= bytes_written;
-    } while (bytes_written != 0 && bytes_to_send > 0);
-  }
-
-  close(s);
-  return 0;
+    return 0;
 }
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index f2d223d..d964d25 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -133,6 +133,7 @@
 public:
     // unlocked objects
     bool mRequestingSid = false;
+    bool mInheritRt = false;
     sp<IBinder> mExtension;
     int mPolicy = SCHED_NORMAL;
     int mPriority = 0;
@@ -327,6 +328,27 @@
     return e->mPriority;
 }
 
+bool BBinder::isInheritRt() {
+    Extras* e = mExtras.load(std::memory_order_acquire);
+
+    return e && e->mInheritRt;
+}
+
+void BBinder::setInheritRt(bool inheritRt) {
+    Extras* e = mExtras.load(std::memory_order_acquire);
+
+    if (!e) {
+        if (!inheritRt) {
+            return;
+        }
+
+        e = getOrCreateExtras();
+        if (!e) return; // out of memory
+    }
+
+    e->mInheritRt = inheritRt;
+}
+
 pid_t BBinder::getDebugPid() {
     return getpid();
 }
diff --git a/libs/binder/BufferedTextOutput.cpp b/libs/binder/BufferedTextOutput.cpp
index 8cf6097..88c85bf 100644
--- a/libs/binder/BufferedTextOutput.cpp
+++ b/libs/binder/BufferedTextOutput.cpp
@@ -18,7 +18,6 @@
 #include <binder/Debug.h>
 
 #include <cutils/atomic.h>
-#include <cutils/threads.h>
 #include <utils/Log.h>
 #include <utils/RefBase.h>
 #include <utils/Vector.h>
@@ -91,22 +90,6 @@
 
 static pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;
 
-static thread_store_t   tls;
-
-BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
-{
-    ThreadState*  ts = (ThreadState*) thread_store_get( &tls );
-    if (ts) return ts;
-    ts = new ThreadState;
-    thread_store_set( &tls, ts, threadDestructor );
-    return ts;
-}
-
-void BufferedTextOutput::threadDestructor(void *st)
-{
-    delete ((ThreadState*)st);
-}
-
 static volatile int32_t gSequence = 0;
 
 static volatile int32_t gFreeBufferIndex = -1;
@@ -266,16 +249,14 @@
 BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
 {
     if ((mFlags&MULTITHREADED) != 0) {
-        ThreadState* ts = getThreadState();
-        if (ts) {
-            while (ts->states.size() <= (size_t)mIndex) ts->states.add(nullptr);
-            BufferState* bs = ts->states[mIndex].get();
-            if (bs != nullptr && bs->seq == mSeq) return bs;
-            
-            ts->states.editItemAt(mIndex) = new BufferState(mIndex);
-            bs = ts->states[mIndex].get();
-            if (bs != nullptr) return bs;
-        }
+        thread_local ThreadState ts;
+        while (ts.states.size() <= (size_t)mIndex) ts.states.add(nullptr);
+        BufferState* bs = ts.states[mIndex].get();
+        if (bs != nullptr && bs->seq == mSeq) return bs;
+
+        ts.states.editItemAt(mIndex) = new BufferState(mIndex);
+        bs = ts.states[mIndex].get();
+        if (bs != nullptr) return bs;
     }
     
     return mGlobalState;
diff --git a/libs/binder/BufferedTextOutput.h b/libs/binder/BufferedTextOutput.h
index 1b27bb2..fdd532a 100644
--- a/libs/binder/BufferedTextOutput.h
+++ b/libs/binder/BufferedTextOutput.h
@@ -47,10 +47,7 @@
 private:
     struct BufferState;
     struct ThreadState;
-    
-    static  ThreadState*getThreadState();
-    static  void        threadDestructor(void *st);
-    
+
             BufferState*getBuffer() const;
             
     uint32_t            mFlags;
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 88a631a..91e465f 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -222,6 +222,9 @@
             if (local->isRequestingSid()) {
                 obj.flags |= FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
             }
+            if (local->isInheritRt()) {
+                obj.flags |= FLAT_BINDER_FLAG_INHERIT_RT;
+            }
             obj.hdr.type = BINDER_TYPE_BINDER;
             obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
             obj.cookie = reinterpret_cast<uintptr_t>(local);
@@ -2055,8 +2058,11 @@
     if (size >= 0 && size < INT32_MAX) {
         *outLen = size;
         const char* str = (const char*)readInplace(size+1);
-        if (str != nullptr && str[size] == '\0') {
-            return str;
+        if (str != nullptr) {
+            if (str[size] == '\0') {
+                return str;
+            }
+            android_errorWriteLog(0x534e4554, "172655291");
         }
     }
     *outLen = 0;
@@ -2138,8 +2144,11 @@
     if (size >= 0 && size < INT32_MAX) {
         *outLen = size;
         const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));
-        if (str != nullptr && str[size] == u'\0') {
-            return str;
+        if (str != nullptr) {
+            if (str[size] == u'\0') {
+                return str;
+            }
+            android_errorWriteLog(0x534e4554, "172655291");
         }
     }
     *outLen = 0;
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 4375818..97e282e 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -38,6 +38,9 @@
       "name": "aidl_lazy_test"
     },
     {
+      "name": "aidl_integration_test"
+    },
+    {
       "name": "libbinderthreadstateutils_test"
     },
     {
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index d6da397..7079544 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -87,6 +87,11 @@
     int                 getMinSchedulerPolicy();
     int                 getMinSchedulerPriority();
 
+    // Whether realtime scheduling policies are inherited.
+    bool                isInheritRt();
+    // This must be called before the object is sent to another process. Not thread safe.
+    void                setInheritRt(bool inheritRt);
+
     pid_t               getDebugPid();
 
 protected:
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index 2d85f90..04167f7 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -114,6 +114,13 @@
      */
     AIBinder** getR() { return &mBinder; }
 
+    bool operator!=(const SpAIBinder& rhs) const { return get() != rhs.get(); }
+    bool operator<(const SpAIBinder& rhs) const { return get() < rhs.get(); }
+    bool operator<=(const SpAIBinder& rhs) const { return get() <= rhs.get(); }
+    bool operator==(const SpAIBinder& rhs) const { return get() == rhs.get(); }
+    bool operator>(const SpAIBinder& rhs) const { return get() > rhs.get(); }
+    bool operator>=(const SpAIBinder& rhs) const { return get() >= rhs.get(); }
+
    private:
     AIBinder* mBinder = nullptr;
 };
@@ -200,6 +207,13 @@
     ~ScopedAParcel() {}
     ScopedAParcel(ScopedAParcel&&) = default;
     ScopedAParcel& operator=(ScopedAParcel&&) = default;
+
+    bool operator!=(const ScopedAParcel& rhs) const { return get() != rhs.get(); }
+    bool operator<(const ScopedAParcel& rhs) const { return get() < rhs.get(); }
+    bool operator<=(const ScopedAParcel& rhs) const { return get() <= rhs.get(); }
+    bool operator==(const ScopedAParcel& rhs) const { return get() == rhs.get(); }
+    bool operator>(const ScopedAParcel& rhs) const { return get() > rhs.get(); }
+    bool operator>=(const ScopedAParcel& rhs) const { return get() >= rhs.get(); }
 };
 
 /**
@@ -323,6 +337,13 @@
     ~ScopedFileDescriptor() {}
     ScopedFileDescriptor(ScopedFileDescriptor&&) = default;
     ScopedFileDescriptor& operator=(ScopedFileDescriptor&&) = default;
+
+    bool operator!=(const ScopedFileDescriptor& rhs) const { return get() != rhs.get(); }
+    bool operator<(const ScopedFileDescriptor& rhs) const { return get() < rhs.get(); }
+    bool operator<=(const ScopedFileDescriptor& rhs) const { return get() <= rhs.get(); }
+    bool operator==(const ScopedFileDescriptor& rhs) const { return get() == rhs.get(); }
+    bool operator>(const ScopedFileDescriptor& rhs) const { return get() > rhs.get(); }
+    bool operator>=(const ScopedFileDescriptor& rhs) const { return get() >= rhs.get(); }
 };
 
 }  // namespace ndk
diff --git a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
index 4858514..cf2b9e9 100644
--- a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
@@ -114,6 +114,25 @@
 
     void reset() { AParcel_reset(mParcel.get()); }
 
+    inline bool operator!=(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) != std::tie(rhs.mParcel, rhs.mStability);
+    }
+    inline bool operator<(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) < std::tie(rhs.mParcel, rhs.mStability);
+    }
+    inline bool operator<=(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) <= std::tie(rhs.mParcel, rhs.mStability);
+    }
+    inline bool operator==(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) == std::tie(rhs.mParcel, rhs.mStability);
+    }
+    inline bool operator>(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) > std::tie(rhs.mParcel, rhs.mStability);
+    }
+    inline bool operator>=(const AParcelableHolder& rhs) const {
+        return std::tie(mParcel, mStability) >= std::tie(rhs.mParcel, rhs.mStability);
+    }
+
    private:
     mutable ndk::ScopedAParcel mParcel;
     parcelable_stability_t mStability;
diff --git a/libs/binder/parcel_fuzzer/main.cpp b/libs/binder/parcel_fuzzer/main.cpp
index 386c70b..78606cc 100644
--- a/libs/binder/parcel_fuzzer/main.cpp
+++ b/libs/binder/parcel_fuzzer/main.cpp
@@ -20,12 +20,16 @@
 #include "hwbinder.h"
 #include "util.h"
 
+#include <iostream>
+
 #include <android-base/logging.h>
 #include <fuzzbinder/random_parcel.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
 #include <cstdlib>
 #include <ctime>
+#include <sys/resource.h>
+#include <sys/time.h>
 
 using android::fillRandomParcel;
 
@@ -77,7 +81,25 @@
     }
 }
 
+size_t getHardMemoryLimit() {
+    struct rlimit limit;
+    CHECK(0 == getrlimit(RLIMIT_AS, &limit)) << errno;
+    return limit.rlim_max;
+}
+
+void setMemoryLimit(size_t cur, size_t max) {
+    const struct rlimit kLimit = {
+       .rlim_cur = cur,
+       .rlim_max = max,
+    };
+    CHECK(0 == setrlimit(RLIMIT_AS, &kLimit)) << errno;
+}
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static constexpr size_t kMemLimit = 1 * 1024 * 1024;
+    size_t hardLimit = getHardMemoryLimit();
+    setMemoryLimit(std::min(kMemLimit, hardLimit), hardLimit);
+
     if (size <= 1) return 0;  // no use
 
     // avoid timeouts, see b/142617274, b/142473153
@@ -102,5 +124,7 @@
 
     provider.PickValueInArray(fuzzBackend)(std::move(provider));
 
+    setMemoryLimit(hardLimit, hardLimit);
+
     return 0;
 }
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index b558f27..ed3b9ec 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -592,7 +592,9 @@
                         // pointer.
                         return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
                     }
-                } else if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
+                }
+
+                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 {
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 497361a..bb8c492 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -482,4 +482,17 @@
         let _interface: Box<dyn ITestSameDescriptor> = FromIBinder::try_from(service.as_binder())
             .expect("Could not re-interpret service as the ITestSameDescriptor interface");
     }
+
+    /// Test that we can round-trip a rust service through a generic IBinder
+    #[test]
+    fn reassociate_rust_binder() {
+        let service_name = "testing_service";
+        let service_ibinder = BnTest::new_binder(TestService { s: service_name.to_string() })
+            .as_binder();
+
+        let service: Box<dyn ITest> = service_ibinder.into_interface()
+            .expect("Could not reassociate the generic ibinder");
+
+        assert_eq!(service.test().unwrap(), service_name);
+    }
 }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index ad4729d..0f7d159 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -30,6 +30,7 @@
 #include <binder/IServiceManager.h>
 
 #include <private/binder/binder_module.h>
+#include <linux/sched.h>
 #include <sys/epoll.h>
 #include <sys/prctl.h>
 
@@ -53,6 +54,7 @@
 
 static constexpr int kSchedPolicy = SCHED_RR;
 static constexpr int kSchedPriority = 7;
+static constexpr int kSchedPriorityMore = 8;
 
 static String16 binderLibTestServiceName = String16("test.binderLib");
 
@@ -1088,6 +1090,25 @@
     EXPECT_EQ(kSchedPriority, priority);
 }
 
+TEST_F(BinderLibTest, InheritRt) {
+    sp<IBinder> server = addServer();
+    ASSERT_TRUE(server != nullptr);
+
+    const struct sched_param param {
+        .sched_priority = kSchedPriorityMore,
+    };
+    EXPECT_EQ(0, sched_setscheduler(getpid(), SCHED_RR, &param));
+
+    Parcel data, reply;
+    status_t ret = server->transact(BINDER_LIB_TEST_GET_SCHEDULING_POLICY, data, &reply);
+    EXPECT_EQ(NO_ERROR, ret);
+
+    int policy = reply.readInt32();
+    int priority = reply.readInt32();
+
+    EXPECT_EQ(kSchedPolicy, policy & (~SCHED_RESET_ON_FORK));
+    EXPECT_EQ(kSchedPriorityMore, priority);
+}
 
 TEST_F(BinderLibTest, VectorSent) {
     Parcel data, reply;
@@ -1460,6 +1481,8 @@
 
         testService->setMinSchedulerPolicy(kSchedPolicy, kSchedPriority);
 
+        testService->setInheritRt(true);
+
         /*
          * Normally would also contain functionality as well, but we are only
          * testing the extension mechanism.
diff --git a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
index 9ac65bb..69f1b9d 100644
--- a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
@@ -37,8 +37,8 @@
                                   bbinder->isRequestingSid();
                               },
                               [](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
-                                  bool request_sid = fdp->ConsumeBool();
-                                  bbinder->setRequestingSid(request_sid);
+                                  bool requestSid = fdp->ConsumeBool();
+                                  bbinder->setRequestingSid(requestSid);
                               },
                               [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
                                   bbinder->getExtension();
@@ -63,6 +63,13 @@
                               [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
                                   bbinder->getMinSchedulerPriority();
                               },
+                              [](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
+                                  bool inheritRt = fdp->ConsumeBool();
+                                  bbinder->setInheritRt(inheritRt);
+                              },
+                              [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
+                                  bbinder->isInheritRt();
+                              },
                               [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
                                   bbinder->getDebugPid();
                               }};
diff --git a/libs/cputimeinstate/Android.bp b/libs/cputimeinstate/Android.bp
index b1943a4..e3cd085 100644
--- a/libs/cputimeinstate/Android.bp
+++ b/libs/cputimeinstate/Android.bp
@@ -33,5 +33,6 @@
         "-Wall",
         "-Wextra",
     ],
+    require_root: true,
 }
 
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 6058430..4209dc5 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -59,6 +59,7 @@
 static unique_fd gTisMapFd;
 static unique_fd gConcurrentMapFd;
 static unique_fd gUidLastUpdateMapFd;
+static unique_fd gPidTisMapFd;
 
 static std::optional<std::vector<uint32_t>> readNumbersFromFile(const std::string &path) {
     std::string data;
@@ -139,6 +140,12 @@
             unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_last_update_map")};
     if (gUidLastUpdateMapFd < 0) return false;
 
+    gPidTisMapFd = unique_fd{mapRetrieveRO(BPF_FS_PATH "map_time_in_state_pid_time_in_state_map")};
+    if (gPidTisMapFd < 0) return false;
+
+    unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+    if (trackedPidMapFd < 0) return false;
+
     gInitialized = true;
     return true;
 }
@@ -222,7 +229,8 @@
     }
 
     gTracking = attachTracepointProgram("sched", "sched_switch") &&
-            attachTracepointProgram("power", "cpu_frequency");
+            attachTracepointProgram("power", "cpu_frequency") &&
+            attachTracepointProgram("sched", "sched_process_free");
     return gTracking;
 }
 
@@ -502,5 +510,106 @@
     return true;
 }
 
+bool startTrackingProcessCpuTimes(pid_t pid) {
+    if (!gInitialized && !initGlobals()) return false;
+
+    unique_fd trackedPidHashMapFd(
+            mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_hash_map"));
+    if (trackedPidHashMapFd < 0) return false;
+
+    unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+    if (trackedPidMapFd < 0) return false;
+
+    for (uint32_t index = 0; index < MAX_TRACKED_PIDS; index++) {
+        // Find first available [index, pid] entry in the pid_tracked_hash_map map
+        if (writeToMapEntry(trackedPidHashMapFd, &index, &pid, BPF_NOEXIST) != 0) {
+            if (errno != EEXIST) {
+                return false;
+            }
+            continue; // This index is already taken
+        }
+
+        tracked_pid_t tracked_pid = {.pid = pid, .state = TRACKED_PID_STATE_ACTIVE};
+        if (writeToMapEntry(trackedPidMapFd, &index, &tracked_pid, BPF_ANY) != 0) {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+// Marks the specified task identified by its PID (aka TID) for CPU time-in-state tracking
+// aggregated with other tasks sharing the same TGID and aggregation key.
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+    if (!gInitialized && !initGlobals()) return false;
+
+    unique_fd taskAggregationMapFd(
+            mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_task_aggregation_map"));
+    if (taskAggregationMapFd < 0) return false;
+
+    return writeToMapEntry(taskAggregationMapFd, &pid, &aggregationKey, BPF_ANY) == 0;
+}
+
+// Retrieves the times in ns that each thread spent running at each CPU freq, aggregated by
+// aggregation key.
+// Return contains no value on error, otherwise it contains a map from aggregation keys
+// to vectors of vectors using the format:
+// { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
+//   aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
+// where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest freq.
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys) {
+    if (!gInitialized && !initGlobals()) return {};
+
+    uint32_t maxFreqCount = 0;
+    std::vector<std::vector<uint64_t>> mapFormat;
+    for (const auto &freqList : gPolicyFreqs) {
+        if (freqList.size() > maxFreqCount) maxFreqCount = freqList.size();
+        mapFormat.emplace_back(freqList.size(), 0);
+    }
+
+    bool dataCollected = false;
+    std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
+    std::vector<tis_val_t> vals(gNCpus);
+    for (uint16_t aggregationKey : aggregationKeys) {
+        map.emplace(aggregationKey, mapFormat);
+
+        aggregated_task_tis_key_t key{.tgid = tgid, .aggregation_key = aggregationKey};
+        for (key.bucket = 0; key.bucket <= (maxFreqCount - 1) / FREQS_PER_ENTRY; ++key.bucket) {
+            if (findMapEntry(gPidTisMapFd, &key, vals.data()) != 0) {
+                if (errno != ENOENT) {
+                    return {};
+                }
+                continue;
+            } else {
+                dataCollected = true;
+            }
+
+            // Combine data by aggregating time-in-state data grouped by CPU cluster aka policy.
+            uint32_t offset = key.bucket * FREQS_PER_ENTRY;
+            uint32_t nextOffset = offset + FREQS_PER_ENTRY;
+            for (uint32_t j = 0; j < gNPolicies; ++j) {
+                if (offset >= gPolicyFreqs[j].size()) continue;
+                auto begin = map[key.aggregation_key][j].begin() + offset;
+                auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY
+                                                               : map[key.aggregation_key][j].end();
+                for (const auto &cpu : gPolicyCpus[j]) {
+                    std::transform(begin, end, std::begin(vals[cpu].ar), begin,
+                                   std::plus<uint64_t>());
+                }
+            }
+        }
+    }
+
+    if (!dataCollected) {
+        // Check if eBPF is supported on this device. If it is, gTisMap should not be empty.
+        time_key_t key;
+        if (getFirstMapKey(gTisMapFd, &key) != 0) {
+            return {};
+        }
+    }
+    return map;
+}
+
 } // namespace bpf
 } // namespace android
diff --git a/libs/cputimeinstate/cputimeinstate.h b/libs/cputimeinstate/cputimeinstate.h
index b7600f5..87a328a 100644
--- a/libs/cputimeinstate/cputimeinstate.h
+++ b/libs/cputimeinstate/cputimeinstate.h
@@ -41,5 +41,10 @@
     getUidsUpdatedConcurrentTimes(uint64_t *lastUpdate);
 bool clearUidTimes(unsigned int uid);
 
+bool startTrackingProcessCpuTimes(pid_t pid);
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys);
+
 } // namespace bpf
 } // namespace android
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 0d5f412..519689b 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -19,6 +19,8 @@
 
 #include <sys/sysinfo.h>
 
+#include <pthread.h>
+#include <semaphore.h>
 #include <numeric>
 #include <unordered_map>
 #include <vector>
@@ -504,5 +506,85 @@
     for (size_t i = 0; i < freqs->size(); ++i) EXPECT_EQ((*freqs)[i].size(), (*times)[i].size());
 }
 
+uint64_t timeNanos() {
+    struct timespec spec;
+    clock_gettime(CLOCK_MONOTONIC, &spec);
+    return spec.tv_sec * 1000000000 + spec.tv_nsec;
+}
+
+// Keeps CPU busy with some number crunching
+void useCpu() {
+    long sum = 0;
+    for (int i = 0; i < 100000; i++) {
+        sum *= i;
+    }
+}
+
+sem_t pingsem, pongsem;
+
+void *testThread(void *) {
+    for (int i = 0; i < 10; i++) {
+        sem_wait(&pingsem);
+        useCpu();
+        sem_post(&pongsem);
+    }
+    return nullptr;
+}
+
+TEST(TimeInStateTest, GetAggregatedTaskCpuFreqTimes) {
+    uint64_t startTimeNs = timeNanos();
+
+    sem_init(&pingsem, 0, 1);
+    sem_init(&pongsem, 0, 0);
+
+    pthread_t thread;
+    ASSERT_EQ(pthread_create(&thread, NULL, &testThread, NULL), 0);
+
+    // This process may have been running for some time, so when we start tracking
+    // CPU time, the very first switch may include the accumulated time.
+    // Yield the remainder of this timeslice to the newly created thread.
+    sem_wait(&pongsem);
+    sem_post(&pingsem);
+
+    pid_t tgid = getpid();
+    startTrackingProcessCpuTimes(tgid);
+
+    pid_t tid = pthread_gettid_np(thread);
+    startAggregatingTaskCpuTimes(tid, 42);
+
+    // Play ping-pong with the other thread to ensure that both threads get
+    // some CPU time.
+    for (int i = 0; i < 9; i++) {
+        sem_wait(&pongsem);
+        useCpu();
+        sem_post(&pingsem);
+    }
+
+    pthread_join(thread, NULL);
+
+    std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> optionalMap =
+            getAggregatedTaskCpuFreqTimes(tgid, {0, 42});
+    ASSERT_TRUE(optionalMap);
+
+    std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map = *optionalMap;
+    ASSERT_EQ(map.size(), 2u);
+
+    uint64_t testDurationNs = timeNanos() - startTimeNs;
+    for (auto pair : map) {
+        uint16_t aggregationKey = pair.first;
+        ASSERT_TRUE(aggregationKey == 0 || aggregationKey == 42);
+
+        std::vector<std::vector<uint64_t>> timesInState = pair.second;
+        uint64_t totalCpuTime = 0;
+        for (size_t i = 0; i < timesInState.size(); i++) {
+            for (size_t j = 0; j < timesInState[i].size(); j++) {
+                totalCpuTime += timesInState[i][j];
+            }
+        }
+        ASSERT_GT(totalCpuTime, 0ul);
+        ASSERT_LE(totalCpuTime, testDurationNs);
+    }
+}
+
 } // namespace bpf
 } // namespace android
diff --git a/libs/gralloc/OWNERS b/libs/gralloc/OWNERS
index 4a95778..93879d8 100644
--- a/libs/gralloc/OWNERS
+++ b/libs/gralloc/OWNERS
@@ -1,2 +1 @@
 chrisforbes@google.com
-vhau@google.com
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
index 1a7c2c9..2f418ac 100644
--- a/libs/gralloc/types/include/gralloctypes/Gralloc4.h
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -32,202 +32,6 @@
 #include <android/hardware/graphics/mapper/4.0/IMapper.h>
 
 namespace android {
-
-/**
- * Define equality operators for Stable AIDL types.
- */
-inline bool operator==(const aidl::android::hardware::graphics::common::ExtendableType& lhs,
-                const aidl::android::hardware::graphics::common::ExtendableType& rhs) {
-    return !std::strcmp(lhs.name.c_str(), rhs.name.c_str()) && lhs.value == rhs.value;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::ExtendableType& lhs,
-                const aidl::android::hardware::graphics::common::ExtendableType& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::PlaneLayoutComponent& lhs,
-                const aidl::android::hardware::graphics::common::PlaneLayoutComponent& rhs) {
-    if (lhs.type.name != rhs.type.name) {
-        return false;
-    }
-    if (lhs.type.value != rhs.type.value) {
-        return false;
-    }
-    if (lhs.sizeInBits != rhs.sizeInBits) {
-        return false;
-    }
-    if (lhs.offsetInBits != rhs.offsetInBits) {
-        return false;
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::PlaneLayoutComponent& lhs,
-                const aidl::android::hardware::graphics::common::PlaneLayoutComponent& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Rect& lhs,
-                const aidl::android::hardware::graphics::common::Rect& rhs) {
-    if (lhs.left != rhs.left) {
-        return false;
-    }
-    if (lhs.top != rhs.top) {
-        return false;
-    }
-    if (lhs.right != rhs.right) {
-        return false;
-    }
-    if (lhs.bottom != rhs.bottom) {
-        return false;
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Rect& lhs,
-                const aidl::android::hardware::graphics::common::Rect& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const std::vector<aidl::android::hardware::graphics::common::Rect>& lhs,
-                const std::vector<aidl::android::hardware::graphics::common::Rect>& rhs) {
-    if (lhs.size() != rhs.size()) {
-        return false;
-    }
-    for (size_t i = 0; i < lhs.size(); i++) {
-        if (lhs[i] != rhs[i]) {
-            return false;
-        }
-    }
-    return true;
-}
-
-inline bool operator!=(const std::vector<aidl::android::hardware::graphics::common::Rect>& lhs,
-                const std::vector<aidl::android::hardware::graphics::common::Rect>& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::PlaneLayout& lhs,
-                const aidl::android::hardware::graphics::common::PlaneLayout& rhs) {
-    if (lhs.offsetInBytes != rhs.offsetInBytes) {
-        return false;
-    }
-    if (lhs.sampleIncrementInBits != rhs.sampleIncrementInBits) {
-        return false;
-    }
-    if (lhs.strideInBytes != rhs.strideInBytes) {
-        return false;
-    }
-    if (lhs.widthInSamples != rhs.widthInSamples) {
-        return false;
-    }
-    if (lhs.heightInSamples != rhs.heightInSamples) {
-        return false;
-    }
-    if (lhs.totalSizeInBytes != rhs.totalSizeInBytes) {
-        return false;
-    }
-    if (lhs.horizontalSubsampling != rhs.horizontalSubsampling) {
-        return false;
-    }
-    if (lhs.verticalSubsampling != rhs.verticalSubsampling) {
-        return false;
-    }
-    if (lhs.components.size() != rhs.components.size()) {
-        return false;
-    }
-    for (size_t i = 0; i < lhs.components.size(); i++) {
-        if (lhs.components[i] != rhs.components[i]) {
-            return false;
-        }
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::PlaneLayout& lhs,
-                const aidl::android::hardware::graphics::common::PlaneLayout& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& lhs,
-                const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& rhs) {
-    if (lhs.size() != rhs.size()) {
-        return false;
-    }
-    for (size_t i = 0; i < lhs.size(); i++) {
-        if (lhs[i] != rhs[i]) {
-            return false;
-        }
-    }
-    return true;
-}
-
-inline bool operator!=(const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& lhs,
-                const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::XyColor& lhs,
-                const aidl::android::hardware::graphics::common::XyColor& rhs) {
-    if (lhs.x != rhs.x) {
-        return false;
-    }
-    if (lhs.y != rhs.y) {
-        return false;
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::XyColor& lhs,
-                const aidl::android::hardware::graphics::common::XyColor& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Smpte2086& lhs,
-                const aidl::android::hardware::graphics::common::Smpte2086& rhs) {
-    if (lhs.primaryRed != rhs.primaryRed) {
-        return false;
-    }
-    if (lhs.primaryGreen != rhs.primaryGreen) {
-        return false;
-    }
-    if (lhs.primaryBlue != rhs.primaryBlue) {
-        return false;
-    }
-    if (lhs.whitePoint != rhs.whitePoint) {
-        return false;
-    }
-    if (lhs.maxLuminance != rhs.maxLuminance) {
-        return false;
-    }
-    if (lhs.minLuminance != rhs.minLuminance) {
-        return false;
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Smpte2086& lhs,
-                const aidl::android::hardware::graphics::common::Smpte2086& rhs) {
-    return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Cta861_3& lhs,
-                const aidl::android::hardware::graphics::common::Cta861_3& rhs) {
-    if (lhs.maxContentLightLevel != rhs.maxContentLightLevel) {
-        return false;
-    }
-    if (lhs.maxFrameAverageLightLevel != rhs.maxFrameAverageLightLevel) {
-        return false;
-    }
-    return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Cta861_3& lhs,
-                const aidl::android::hardware::graphics::common::Cta861_3& rhs) {
-    return !(lhs == rhs);
-}
-
 namespace gralloc4 {
 
 #define GRALLOC4_STANDARD_METADATA_TYPE "android.hardware.graphics.common.StandardMetadataType"
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 3a3a96f..b9ab561 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -150,6 +150,16 @@
     if (mRequestedSize != newSize) {
         mRequestedSize.set(newSize);
         mBufferItemConsumer->setDefaultBufferSize(mRequestedSize.width, mRequestedSize.height);
+        if (mLastBufferScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
+            // If the buffer supports scaling, update the frame immediately since the client may
+            // want to scale the existing buffer to the new size.
+            mSize = mRequestedSize;
+            SurfaceComposerClient::Transaction t;
+            t.setFrame(mSurfaceControl,
+                       {0, 0, static_cast<int32_t>(mSize.width),
+                        static_cast<int32_t>(mSize.height)});
+            t.apply();
+        }
     }
 }
 
@@ -276,6 +286,8 @@
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
 
+    mLastBufferScalingMode = bufferItem.mScalingMode;
+
     t->setBuffer(mSurfaceControl, buffer);
     t->setAcquireFence(mSurfaceControl,
                        bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
@@ -348,7 +360,6 @@
 
 bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
     if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
-        mSize = mRequestedSize;
         // Only reject buffers if scaling mode is freeze.
         return false;
     }
diff --git a/libs/gui/OWNERS b/libs/gui/OWNERS
index ecccf29..1667fb0 100644
--- a/libs/gui/OWNERS
+++ b/libs/gui/OWNERS
@@ -7,9 +7,7 @@
 lpy@google.com
 mathias@google.com
 racarr@google.com
-steventhomas@google.com
 stoza@google.com
-vhau@google.com
 vishnun@google.com
 
 per-file EndToEndNativeInputTest.cpp = svv@google.com
@@ -22,4 +20,4 @@
 per-file include/gui/BufferQueue* = set noparent
 per-file include/gui/BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com
 per-file include/gui/IGraphicBuffer* = set noparent
-per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
\ No newline at end of file
+per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index bdf128a..1139390 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -146,6 +146,11 @@
     bool mAutoRefresh GUARDED_BY(mMutex) = false;
 
     std::queue<int64_t> mNextFrameTimelineVsyncIdQueue GUARDED_BY(mMutex);
+
+    // Last acquired buffer's scaling mode. This is used to check if we should update the blast
+    // layer size immediately or wait until we get the next buffer. This will support scenarios
+    // where the layer can change sizes and the buffer will scale to fit the new size.
+    uint32_t mLastBufferScalingMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
 };
 
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 82c220f..6316b59 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -21,4 +21,11 @@
 interface IInputConstants
 {
     const int DEFAULT_DISPATCHING_TIMEOUT_MILLIS = 5000; // 5 seconds
+
+    // Compatibility changes.
+    /**
+      * TODO(b/157929241): remove this before closing the bug. This is needed temporarily
+      * to identify apps that are using this flag.
+      */
+    const long BLOCK_FLAG_SLIPPERY = 157929241;
 }
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index b878150..0e74c63 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -81,6 +81,9 @@
         "skia/AutoBackendTexture.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
+        "skia/debug/CaptureTimer.cpp",
+        "skia/debug/CommonPool.cpp",
+        "skia/debug/SkiaCapture.cpp",
         "skia/filters/BlurFilter.cpp",
         "skia/filters/LinearEffect.cpp",
     ],
@@ -94,6 +97,7 @@
     cflags: [
         "-fvisibility=hidden",
         "-Werror=format",
+        "-Wno-unused-parameter",
     ],
     srcs: [
         ":librenderengine_sources",
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 11b8e44..ef12fd2 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -31,10 +31,17 @@
 #include <ui/Transform.h>
 
 /**
- * Allows to set RenderEngine backend to GLES (default) or Vulkan (NOT yet supported).
+ * Allows to set RenderEngine backend to GLES (default) or SkiaGL (NOT yet supported).
  */
 #define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
 
+/**
+ * Turns on recording of skia commands in SkiaGL version of the RE. This property
+ * defines number of milliseconds for the recording to take place. A non zero value
+ * turns on the recording.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS "debug.renderengine.capture_skia_ms"
+
 struct ANativeWindowBuffer;
 
 namespace android {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index c7e65a1..4e9c5af 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -15,19 +15,19 @@
  */
 
 //#define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include <cstdint>
 #include <memory>
 
 #include "SkImageInfo.h"
 #include "log/log_main.h"
 #include "system/graphics-base-v1.0.h"
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
-#include <GLES2/gl2.h>
 #include <sync/sync.h>
 #include <ui/BlurRegion.h>
 #include <ui/GraphicBuffer.h>
@@ -35,6 +35,8 @@
 #include "../gl/GLExtensions.h"
 #include "SkiaGLRenderEngine.h"
 #include "filters/BlurFilter.h"
+#include "filters/LinearEffect.h"
+#include "skia/debug/SkiaCapture.h"
 
 #include <GrContextOptions.h>
 #include <SkCanvas.h>
@@ -46,17 +48,9 @@
 #include <SkShadowUtils.h>
 #include <SkSurface.h>
 #include <gl/GrGLInterface.h>
-#include <sync/sync.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/Trace.h>
 
 #include <cmath>
 
-#include "../gl/GLExtensions.h"
-#include "SkiaGLRenderEngine.h"
-#include "filters/BlurFilter.h"
-#include "filters/LinearEffect.h"
-
 bool checkGlError(const char* op, int lineNumber);
 
 namespace android {
@@ -482,7 +476,7 @@
                                                                         : ui::Dataspace::SRGB,
                                                                 mGrContext.get());
 
-    auto canvas = surface->getCanvas();
+    SkCanvas* canvas = mCapture.tryCapture(surface.get());
     // Clear the entire canvas with a transparent black to prevent ghost images.
     canvas->clear(SK_ColorTRANSPARENT);
     canvas->save();
@@ -492,8 +486,7 @@
     // displays might have different scaling when compared to the physical screen.
 
     canvas->clipRect(getSkRect(display.physicalDisplay));
-    SkMatrix screenTransform;
-    screenTransform.setTranslate(display.physicalDisplay.left, display.physicalDisplay.top);
+    canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);
 
     const auto clipWidth = display.clip.width();
     const auto clipHeight = display.clip.height();
@@ -507,31 +500,33 @@
             static_cast<SkScalar>(rotatedClipWidth);
     const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
             static_cast<SkScalar>(rotatedClipHeight);
-    screenTransform.preScale(scaleX, scaleY);
+    canvas->scale(scaleX, scaleY);
 
     // Canvas rotation is done by centering the clip window at the origin, rotating, translating
     // back so that the top left corner of the clip is at (0, 0).
-    screenTransform.preTranslate(rotatedClipWidth / 2, rotatedClipHeight / 2);
-    screenTransform.preRotate(toDegrees(display.orientation));
-    screenTransform.preTranslate(-clipWidth / 2, -clipHeight / 2);
-    screenTransform.preTranslate(-display.clip.left, -display.clip.top);
+    canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
+    canvas->rotate(toDegrees(display.orientation));
+    canvas->translate(-clipWidth / 2, -clipHeight / 2);
+    canvas->translate(-display.clip.left, -display.clip.top);
     for (const auto& layer : layers) {
-        const SkMatrix drawTransform = getDrawTransform(layer, screenTransform);
+        canvas->save();
+
+        // Layers have a local transform that should be applied to them
+        canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
 
         SkPaint paint;
         const auto& bounds = layer->geometry.boundaries;
         const auto dest = getSkRect(bounds);
+        const auto layerRect = canvas->getTotalMatrix().mapRect(dest);
         std::unordered_map<uint32_t, sk_sp<SkSurface>> cachedBlurs;
-
         if (mBlurFilter) {
-            const auto layerRect = drawTransform.mapRect(dest);
             if (layer->backgroundBlurRadius > 0) {
                 ATRACE_NAME("BackgroundBlur");
                 auto blurredSurface = mBlurFilter->generate(canvas, surface,
                                                             layer->backgroundBlurRadius, layerRect);
                 cachedBlurs[layer->backgroundBlurRadius] = blurredSurface;
 
-                drawBlurRegion(canvas, getBlurRegion(layer), drawTransform, blurredSurface);
+                drawBlurRegion(canvas, getBlurRegion(layer), layerRect, blurredSurface);
             }
             if (layer->blurRegions.size() > 0) {
                 for (auto region : layer->blurRegions) {
@@ -549,8 +544,6 @@
         if (layer->source.buffer.buffer) {
             ATRACE_NAME("DrawImage");
             const auto& item = layer->source.buffer;
-            const auto bufferWidth = item.buffer->getBounds().width();
-            const auto bufferHeight = item.buffer->getBounds().height();
             std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
             auto iter = mTextureCache.find(item.buffer->getId());
             if (iter != mTextureCache.end()) {
@@ -582,50 +575,19 @@
                                                                  ? kPremul_SkAlphaType
                                                                  : kUnpremul_SkAlphaType),
                                         mGrContext.get());
-            SkMatrix matrix;
-            if (layer->geometry.roundedCornersRadius > 0) {
-                const auto roundedRect = getRoundedRect(layer);
-                matrix.setTranslate(roundedRect.getBounds().left() - dest.left(),
-                                    roundedRect.getBounds().top() - dest.top());
-            } else {
-                matrix.setIdentity();
-            }
 
             auto texMatrix = getSkM44(item.textureTransform).asM33();
-
-            // b/171404534, scale to fix the layer
-            matrix.postScale(bounds.getWidth() / bufferWidth, bounds.getHeight() / bufferHeight);
-
             // textureTansform was intended to be passed directly into a shader, so when
             // building the total matrix with the textureTransform we need to first
             // normalize it, then apply the textureTransform, then scale back up.
-            matrix.postScale(1.0f / bufferWidth, 1.0f / bufferHeight);
+            texMatrix.preScale(1.0f / bounds.getWidth(), 1.0f / bounds.getHeight());
+            texMatrix.postScale(image->width(), image->height());
 
-            auto rotatedBufferWidth = bufferWidth;
-            auto rotatedBufferHeight = bufferHeight;
-
-            // Swap the buffer width and height if we're rotating, so that we
-            // scale back up by the correct factors post-rotation.
-            if (texMatrix.getSkewX() <= -0.5f || texMatrix.getSkewX() >= 0.5f) {
-                std::swap(rotatedBufferWidth, rotatedBufferHeight);
-                // TODO: clean this up.
-                // GLESRenderEngine specifies its texture coordinates in
-                // CW orientation under OpenGL conventions, when they probably should have
-                // been CCW instead. The net result is that orientation
-                // transforms are applied in the reverse
-                // direction to render the correct result, because SurfaceFlinger uses the inverse
-                // of the display transform to correct for that. But this means that
-                // the tex transform passed by SkiaGLRenderEngine will rotate
-                // individual layers in the reverse orientation. Hack around it
-                // by injected a 180 degree rotation, but ultimately this is
-                // a bug in how SurfaceFlinger invokes the RenderEngine
-                // interface, so the proper fix should live there, and GLESRenderEngine
-                // should be fixed accordingly.
-                matrix.postRotate(180, 0.5, 0.5);
+            SkMatrix matrix;
+            if (!texMatrix.invert(&matrix)) {
+                matrix = texMatrix;
             }
 
-            matrix.postConcat(texMatrix);
-            matrix.postScale(rotatedBufferWidth, rotatedBufferHeight);
             sk_sp<SkShader> shader;
 
             if (layer->source.buffer.useTextureFiltering) {
@@ -677,12 +639,9 @@
         paint.setColorFilter(SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform)));
 
         for (const auto effectRegion : layer->blurRegions) {
-            drawBlurRegion(canvas, effectRegion, drawTransform,
-                           cachedBlurs[effectRegion.blurRadius]);
+            drawBlurRegion(canvas, effectRegion, layerRect, cachedBlurs[effectRegion.blurRadius]);
         }
 
-        canvas->save();
-        canvas->concat(drawTransform);
         if (layer->shadow.length > 0) {
             const auto rect = layer->geometry.roundedCornersRadius > 0
                     ? getSkRect(layer->geometry.roundedCornersCrop)
@@ -698,11 +657,12 @@
 
         canvas->restore();
     }
+    canvas->restore();
+    mCapture.endCapture();
     {
         ATRACE_NAME("flush surface");
         surface->flush();
     }
-    canvas->restore();
 
     if (drawFence != nullptr) {
         *drawFence = flush();
@@ -738,7 +698,7 @@
 }
 
 inline SkRRect SkiaGLRenderEngine::getRoundedRect(const LayerSettings* layer) {
-    const auto rect = getSkRect(layer->geometry.roundedCornersCrop);
+    const auto rect = getSkRect(layer->geometry.boundaries);
     const auto cornerRadius = layer->geometry.roundedCornersRadius;
     return SkRRect::MakeRectXY(rect, cornerRadius, cornerRadius);
 }
@@ -769,13 +729,6 @@
                  matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);
 }
 
-inline SkMatrix SkiaGLRenderEngine::getDrawTransform(const LayerSettings* layer,
-                                                     const SkMatrix& screenTransform) {
-    // Layers have a local transform matrix that should be applied to them.
-    const auto layerTransform = getSkM44(layer->geometry.positionTransform).asM33();
-    return SkMatrix::Concat(screenTransform, layerTransform);
-}
-
 inline SkPoint3 SkiaGLRenderEngine::getSkPoint3(const vec3& vector) {
     return SkPoint3::Make(vector.x, vector.y, vector.z);
 }
@@ -805,18 +758,20 @@
 }
 
 void SkiaGLRenderEngine::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
-                                        const SkMatrix& drawTransform,
-                                        sk_sp<SkSurface> blurredSurface) {
+                                        const SkRect& layerRect, sk_sp<SkSurface> blurredSurface) {
     ATRACE_CALL();
 
     SkPaint paint;
     paint.setAlpha(static_cast<int>(effectRegion.alpha * 255));
-    const auto matrix = mBlurFilter->getShaderMatrix();
-    paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(matrix));
+    const auto matrix = getBlurShaderTransform(canvas, layerRect);
+    paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(
+            SkTileMode::kClamp,
+            SkTileMode::kClamp,
+            SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+            &matrix));
 
     auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
                                  effectRegion.bottom);
-    drawTransform.mapRect(&rect);
 
     if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
         effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
@@ -833,6 +788,24 @@
     }
 }
 
+SkMatrix SkiaGLRenderEngine::getBlurShaderTransform(const SkCanvas* canvas,
+                                                    const SkRect& layerRect) {
+    // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
+    auto matrix = mBlurFilter->getShaderMatrix();
+    // 2. Since the blurred surface has the size of the layer, we align it with the
+    // top left corner of the layer position.
+    matrix.postConcat(SkMatrix::Translate(layerRect.fLeft, layerRect.fTop));
+    // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
+    // original surface orientation. The inverse matrix has to be applied to align the blur
+    // surface with the current orientation/position of the canvas.
+    SkMatrix drawInverse;
+    if (canvas->getTotalMatrix().invert(&drawInverse)) {
+        matrix.postConcat(drawInverse);
+    }
+
+    return matrix;
+}
+
 EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
                                                 EGLContext shareContext, bool useContextPriority,
                                                 Protection protection) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index f5eed1e..b53250e 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -35,6 +35,7 @@
 #include "SkiaRenderEngine.h"
 #include "android-base/macros.h"
 #include "filters/BlurFilter.h"
+#include "skia/debug/SkiaCapture.h"
 #include "skia/filters/LinearEffect.h"
 
 namespace android {
@@ -77,15 +78,15 @@
     inline BlurRegion getBlurRegion(const LayerSettings* layer);
     inline SkColor getSkColor(const vec4& color);
     inline SkM44 getSkM44(const mat4& matrix);
-    inline SkMatrix getDrawTransform(const LayerSettings* layer, const SkMatrix& screenTransform);
     inline SkPoint3 getSkPoint3(const vec3& vector);
 
     base::unique_fd flush();
     bool waitFence(base::unique_fd fenceFd);
     void drawShadow(SkCanvas* canvas, const SkRect& casterRect, float casterCornerRadius,
                     const ShadowSettings& shadowSettings);
-    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion,
-                        const SkMatrix& drawTransform, sk_sp<SkSurface> blurrendSurface);
+    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& layerRect,
+                        sk_sp<SkSurface> blurredSurface);
+    SkMatrix getBlurShaderTransform(const SkCanvas* canvas, const SkRect& layerRect);
 
     EGLDisplay mEGLDisplay;
     EGLContext mEGLContext;
@@ -116,6 +117,8 @@
     sk_sp<GrDirectContext> mProtectedGrContext;
 
     bool mInProtectedContext = false;
+    // Object to capture commands send to Skia.
+    SkiaCapture mCapture;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/debug/CaptureTimer.cpp b/libs/renderengine/skia/debug/CaptureTimer.cpp
new file mode 100644
index 0000000..11bcdb8
--- /dev/null
+++ b/libs/renderengine/skia/debug/CaptureTimer.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CaptureTimer.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "CommonPool.h"
+
+#include <thread>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+void CaptureTimer::setTimeout(TimeoutCallback function, std::chrono::milliseconds delay) {
+    this->clear = false;
+    CommonPool::post([=]() {
+        if (this->clear) return;
+        std::this_thread::sleep_for(delay);
+        if (this->clear) return;
+        function();
+    });
+}
+
+void CaptureTimer::stop() {
+    this->clear = true;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CaptureTimer.h b/libs/renderengine/skia/debug/CaptureTimer.h
new file mode 100644
index 0000000..a0aa302
--- /dev/null
+++ b/libs/renderengine/skia/debug/CaptureTimer.h
@@ -0,0 +1,43 @@
+/*
+ * 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 <chrono>
+#include <functional>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * Simple timer that times out after a given delay and executes a void
+ * callback function.
+ */
+class CaptureTimer {
+    bool clear = false;
+
+public:
+    using TimeoutCallback = std::function<void()>;
+    // Start the timeout.
+    void setTimeout(TimeoutCallback function, std::chrono::milliseconds delay);
+    // Stop and clean up.
+    void stop();
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CommonPool.cpp b/libs/renderengine/skia/debug/CommonPool.cpp
new file mode 100644
index 0000000..bf15300
--- /dev/null
+++ b/libs/renderengine/skia/debug/CommonPool.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CommonPool.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <sys/resource.h>
+#include <utils/Trace.h>
+
+#include <system/thread_defs.h>
+#include <array>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+CommonPool::CommonPool() {
+    ATRACE_CALL();
+
+    CommonPool* pool = this;
+    // Create 2 workers
+    for (int i = 0; i < THREAD_COUNT; i++) {
+        std::thread worker([pool, i] {
+            {
+                std::array<char, 20> name{"reTask"};
+                snprintf(name.data(), name.size(), "reTask%d", i);
+                auto self = pthread_self();
+                pthread_setname_np(self, name.data());
+                setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_FOREGROUND);
+            }
+            pool->workerLoop();
+        });
+        worker.detach();
+    }
+}
+
+CommonPool& CommonPool::instance() {
+    static CommonPool pool;
+    return pool;
+}
+
+void CommonPool::post(Task&& task) {
+    instance().enqueue(std::move(task));
+}
+
+void CommonPool::enqueue(Task&& task) {
+    std::unique_lock lock(mLock);
+    while (mWorkQueue.size() > QUEUE_SIZE) {
+        lock.unlock();
+        ALOGW("Queue is full: %d, waiting before adding more tasks.", QUEUE_SIZE);
+        usleep(100);
+        lock.lock();
+    }
+    mWorkQueue.push(std::move(task));
+    if (mWaitingThreads == THREAD_COUNT || (mWaitingThreads > 0 && mWorkQueue.size() > 1)) {
+        mCondition.notify_one();
+    }
+}
+
+void CommonPool::workerLoop() {
+    std::unique_lock lock(mLock);
+    while (true) {
+        if (mWorkQueue.size() == 0) {
+            mWaitingThreads++;
+            mCondition.wait(lock);
+            mWaitingThreads--;
+        }
+        // Need to double-check that work is still available now that we have the lock
+        // It may have already been grabbed by a different thread
+        while (mWorkQueue.size() > 0) {
+            auto work = mWorkQueue.front();
+            mWorkQueue.pop();
+            lock.unlock();
+            work();
+            lock.lock();
+        }
+    }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CommonPool.h b/libs/renderengine/skia/debug/CommonPool.h
new file mode 100644
index 0000000..7fc3d23
--- /dev/null
+++ b/libs/renderengine/skia/debug/CommonPool.h
@@ -0,0 +1,70 @@
+/*
+ * 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 <log/log.h>
+
+#include <condition_variable>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <queue>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+namespace {
+#define PREVENT_COPY_AND_ASSIGN(Type) \
+private:                              \
+    Type(const Type&) = delete;       \
+    void operator=(const Type&) = delete
+} // namespace
+
+/**
+ * Shamelessly copied from HWUI to execute Skia Capturing on the back thread in
+ * a safe manner.
+ */
+class CommonPool {
+    PREVENT_COPY_AND_ASSIGN(CommonPool);
+
+public:
+    using Task = std::function<void()>;
+    static constexpr auto THREAD_COUNT = 2;
+    static constexpr auto QUEUE_SIZE = 128;
+
+    static void post(Task&& func);
+
+private:
+    static CommonPool& instance();
+
+    CommonPool();
+    ~CommonPool() {}
+
+    void enqueue(Task&&);
+
+    void workerLoop();
+
+    std::mutex mLock;
+    std::condition_variable mCondition;
+    int mWaitingThreads = 0;
+    std::queue<Task> mWorkQueue;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/debug/README.md b/libs/renderengine/skia/debug/README.md
new file mode 100644
index 0000000..4719e34
--- /dev/null
+++ b/libs/renderengine/skia/debug/README.md
@@ -0,0 +1,17 @@
+This library turns on recording of skia commands in SkiaGL version of the RE.
+The debug property defines number of milliseconds for the recording to take place.
+A non zero value turns on the recording. The recording will stop after MS specified.
+To reset the recording, set the capture_skia_ms flag to a new time. When recording
+is finished, the capture_skia_ms flag will be set to 0 to avoid circular recording.
+
+In order to allow the process to write files onto the device run:
+adb shell setenforce 0
+
+To start recording run:
+adb shell setprop debug.renderengine.capture_skia_ms 1000
+
+File will be stored in the /data/user/ directory on the device:
+adb shell ls -al /data/user/
+
+To retrieve the data from the device:
+adb pull /data/user/re_skiacapture_<timestamp>.mskp
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
new file mode 100644
index 0000000..8006a11
--- /dev/null
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkiaCapture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <utils/Trace.h>
+
+#include "CommonPool.h"
+#include "src/utils/SkMultiPictureDocument.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// The root of the filename to write a recorded SKP to. In order for this file to
+// be written to /data/user/, user must run 'adb shell setenforce 0' in the device.
+static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+
+SkiaCapture::~SkiaCapture() {
+    mTimer.stop();
+}
+
+SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) {
+    ATRACE_CALL();
+
+    // If we are not running yet, set up.
+    if (!mCaptureRunning) {
+        mTimerInterval = std::chrono::milliseconds(
+                base::GetIntProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, 0));
+        // Set up the multi-frame capture. If we fail to set it up, then just return canvas.
+        // If interval is 0, return surface.
+        if (CC_LIKELY(mTimerInterval == 0ms || !setupMultiFrameCapture())) {
+            return surface->getCanvas();
+        }
+        // Start the new timer. When timer expires, write to file.
+        mTimer.setTimeout(
+                [this] {
+                    endCapture();
+                    writeToFile();
+                    // To avoid going in circles, set the flag to 0. This way the capture can be
+                    // restarted just by setting the flag and without restarting the process.
+                    base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0");
+                },
+                mTimerInterval);
+    }
+
+    // Create a canvas pointer, fill it.
+    SkCanvas* pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height());
+
+    // Setting up an nway canvas is common to any kind of capture.
+    mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+    mNwayCanvas->addCanvas(surface->getCanvas());
+    mNwayCanvas->addCanvas(pictureCanvas);
+
+    return mNwayCanvas.get();
+}
+
+void SkiaCapture::endCapture() {
+    ATRACE_CALL();
+    // Don't end anything if we are not running.
+    if (!mCaptureRunning) {
+        return;
+    }
+    // Reset the canvas pointer.
+    mNwayCanvas.reset();
+    // End page.
+    if (mMultiPic) {
+        mMultiPic->endPage();
+    }
+}
+
+void SkiaCapture::writeToFile() {
+    ATRACE_CALL();
+    // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will
+    // handle the heavyweight serialization work and destroy them.
+    // mOpenMultiPicStream is released to a bare pointer because keeping it in
+    // a smart pointer makes the lambda non-copyable. The lambda is only called
+    // once, so this is safe.
+    SkFILEWStream* stream = mOpenMultiPicStream.release();
+    CommonPool::post([doc = std::move(mMultiPic), stream] {
+        ALOGD("Finalizing multi frame SKP");
+        doc->close();
+        delete stream;
+        ALOGD("Multi frame SKP complete.");
+    });
+    mCaptureRunning = false;
+}
+
+bool SkiaCapture::setupMultiFrameCapture() {
+    ATRACE_CALL();
+    ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
+
+    std::string captureFile;
+    // Attach a timestamp to the file.
+    base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+                        std::chrono::steady_clock::now().time_since_epoch().count());
+    auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str());
+    // We own this stream and need to hold it until close() finishes.
+    if (stream->isValid()) {
+        mOpenMultiPicStream = std::move(stream);
+        mSerialContext.reset(new SkSharingSerialContext());
+        SkSerialProcs procs;
+        procs.fImageProc = SkSharingSerialContext::serializeImage;
+        procs.fImageCtx = mSerialContext.get();
+        procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
+            return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
+        };
+        // SkDocuments don't take ownership of the streams they write.
+        // we need to keep it until after mMultiPic.close()
+        // procs is passed as a pointer, but just as a method of having an optional default.
+        // procs doesn't need to outlive this Make call.
+        mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs);
+        mCaptureRunning = true;
+        return true;
+    } else {
+        ALOGE("Could not open \"%s\" for writing.", captureFile.c_str());
+        return false;
+    }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
new file mode 100644
index 0000000..52717a7
--- /dev/null
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -0,0 +1,70 @@
+/*
+ * 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 <SkDocument.h>
+#include <SkNWayCanvas.h>
+#include <SkSurface.h>
+#include <chrono>
+#include "CaptureTimer.h"
+#include "tools/SkSharingProc.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+using namespace std::chrono_literals;
+
+/**
+ * Class that captures frames that are sent to Skia in Render Engine. It sets up
+ * a multi frame capture and writes it into a file on the device. The capture is
+ * done based on a timer.
+ */
+class SkiaCapture {
+    using Interval = std::chrono::milliseconds;
+
+public:
+    SkiaCapture() {}
+    virtual ~SkiaCapture();
+    // Called every frame. Normally returns early with screen canvas.
+    // But when capture is enabled, returns an nwaycanvas where commands are also recorded.
+    SkCanvas* tryCapture(SkSurface* surface);
+    // Called at the end of every frame.
+    void endCapture();
+
+private:
+    // Performs the first-frame work of a multi frame SKP capture. Returns true if successful.
+    bool setupMultiFrameCapture();
+
+    // Closes the recording and serializes sequence to a file.
+    void writeToFile();
+
+    // Multi frame serialization stream and writer used when serializing more than one frame.
+    std::unique_ptr<SkFILEWStream> mOpenMultiPicStream;
+    sk_sp<SkDocument> mMultiPic;
+    std::unique_ptr<SkSharingSerialContext> mSerialContext;
+    std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+
+    // Capturing and interval control.
+    bool mCaptureRunning = false;
+    CaptureTimer mTimer;
+    Interval mTimerInterval = 0ms;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index f19b64a..8927be8 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -66,11 +66,10 @@
     float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
     float radiusByPasses = tmpRadius / (float)numberOfPasses;
 
-    SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale,
-                                                        (float)input->height() * kInputScale);
+    SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)rect.width() * kInputScale,
+                                                        (float)rect.height() * kInputScale);
+    SkRect scaledRect = SkRect::MakeWH(scaledInfo.width(), scaledInfo.height());
 
-    SkRect scaledRect = {rect.fLeft * kInputScale, rect.fTop * kInputScale,
-                         rect.fRight * kInputScale, rect.fBottom * kInputScale};
     auto drawSurface = canvas->makeSurface(scaledInfo);
 
     const float stepX = radiusByPasses;
@@ -80,7 +79,8 @@
     SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
     SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
     blurBuilder.child("input") =
-            input->makeImageSnapshot()->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+            input->makeImageSnapshot(rect.round())
+                    ->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
     blurBuilder.uniform("in_inverseScale") = kInverseInputScale;
     blurBuilder.uniform("in_blurOffset") =
             SkV2{stepX * kInverseInputScale, stepY * kInverseInputScale};
@@ -127,10 +127,6 @@
         lastDrawTarget = readSurface;
     }
 
-    {
-        ATRACE_NAME("Flush Offscreen Surfaces");
-        lastDrawTarget->flushAndSubmit();
-    }
     return lastDrawTarget;
 }
 
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 0e11c99..e825742 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -88,6 +88,45 @@
     }
 };
 
+class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "SkiaGLESRenderEngineFactory"; }
+
+    std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
+        renderengine::RenderEngineCreationArgs reCreationArgs =
+                renderengine::RenderEngineCreationArgs::Builder()
+                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                        .setImageCacheSize(1)
+                        .setEnableProtectedContext(false)
+                        .setPrecacheToneMapperShaderOnly(false)
+                        .setSupportsBackgroundBlur(true)
+                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+                        .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::SKIA_GL)
+                        .build();
+        return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+    }
+};
+
+class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "SkiaGLESCMRenderEngineFactory"; }
+
+    std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
+        renderengine::RenderEngineCreationArgs reCreationArgs =
+                renderengine::RenderEngineCreationArgs::Builder()
+                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                        .setImageCacheSize(1)
+                        .setEnableProtectedContext(false)
+                        .setPrecacheToneMapperShaderOnly(false)
+                        .setSupportsBackgroundBlur(true)
+                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+                        .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::SKIA_GL)
+                        .setUseColorManagerment(true)
+                        .build();
+        return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+    }
+};
+
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
 public:
     static sp<GraphicBuffer> allocateDefaultBuffer() {
@@ -1045,7 +1084,9 @@
 
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<GLESRenderEngineFactory>(),
-                                         std::make_shared<GLESCMRenderEngineFactory>()));
+                                         std::make_shared<GLESCMRenderEngineFactory>(),
+                                         std::make_shared<SkiaGLESRenderEngineFactory>(),
+                                         std::make_shared<SkiaGLESCMRenderEngineFactory>()));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
     const auto& renderEngineFactory = GetParam();
diff --git a/libs/ui/include/ui/FatVector.h b/libs/ui/include/ui/FatVector.h
index 25fe3a0..cb61e6a 100644
--- a/libs/ui/include/ui/FatVector.h
+++ b/libs/ui/include/ui/FatVector.h
@@ -82,6 +82,12 @@
         this->reserve(SIZE);
     }
 
+    FatVector(std::initializer_list<T> init)
+          : std::vector<T, InlineStdAllocator<T, SIZE>>(init,
+                                                        InlineStdAllocator<T, SIZE>(mAllocation)) {
+        this->reserve(SIZE);
+    }
+
     explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); }
 
 private:
diff --git a/libs/vibrator/OWNERS b/libs/vibrator/OWNERS
new file mode 100644
index 0000000..0997e9f
--- /dev/null
+++ b/libs/vibrator/OWNERS
@@ -0,0 +1,2 @@
+lsandrade@google.com
+michaelwr@google.com
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 96e6207..b640e9c 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -58,6 +58,7 @@
         "libstatslog",
         "libutils",
         "libui",
+        "lib-platform-compat-native-api",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/VibrationElement.cpp b/services/inputflinger/VibrationElement.cpp
index aaf5834..17e1ad4 100644
--- a/services/inputflinger/VibrationElement.cpp
+++ b/services/inputflinger/VibrationElement.cpp
@@ -24,13 +24,28 @@
 using android::base::StringPrintf;
 
 namespace android {
+// VibrationElement implementations
+VibrationElement::VibrationElement(size_t channelNum) {
+    channels.reserve(channelNum);
+}
+
+VibrationElement::VibrationElement(const VibrationElement& other) {
+    duration = other.duration;
+    channels.resize(other.channels.size());
+    for (size_t i = 0; i < other.channels.size(); i++) {
+        channels[i].first = other.channels[i].first;
+        channels[i].second = other.channels[i].second;
+    }
+}
 
 const std::string VibrationElement::toString() const {
     std::string dump;
     dump += StringPrintf("[duration=%lldms, channels=[", duration.count());
 
     for (auto it = channels.begin(); it != channels.end(); ++it) {
-        dump += std::to_string(*it);
+        dump += std::to_string(it->first);
+        dump += " : ";
+        dump += std::to_string(it->second);
         if (std::next(it) != channels.end()) {
             dump += ", ";
         }
@@ -40,17 +55,79 @@
     return dump;
 }
 
-uint16_t VibrationElement::getMagnitude(size_t channelIdx) const {
-    if (channelIdx >= channels.size()) {
+uint16_t VibrationElement::getMagnitude(int32_t vibratorId) const {
+    auto it =
+            std::find_if(channels.begin(), channels.end(),
+                         [vibratorId](const std::pair<int32_t /*vibratorId*/, uint8_t /*amplitude*/>
+                                              pair) { return pair.first == vibratorId; });
+    if (it == channels.end()) {
         return 0;
     }
     // convert range [0,255] to [0,65535] (android framework to linux ff ranges)
-    return static_cast<uint16_t>(channels[channelIdx]) << 8;
+    return static_cast<uint16_t>(it->second) << 8;
 }
 
 bool VibrationElement::isOn() const {
     return std::any_of(channels.begin(), channels.end(),
-                       [](uint16_t channel) { return channel != 0; });
+                       [](const auto& channel) { return channel.second != 0; });
+}
+
+void VibrationElement::addChannel(int32_t vibratorId, uint8_t amplitude) {
+    channels.push_back(std::make_pair(vibratorId, amplitude));
+}
+
+bool VibrationElement::operator==(const VibrationElement& other) const {
+    if (duration != other.duration || channels.size() != other.channels.size()) {
+        return false;
+    }
+    for (size_t i = 0; i < CHANNEL_SIZE; i++) {
+        if (channels[i] != other.channels[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool VibrationElement::operator!=(const VibrationElement& other) const {
+    return !(*this == other);
+}
+
+// VibrationSequence implementations
+VibrationSequence::VibrationSequence(size_t length) {
+    pattern.reserve(length);
+}
+
+void VibrationSequence::operator=(const VibrationSequence& other) {
+    pattern = other.pattern;
+}
+
+bool VibrationSequence::operator==(const VibrationSequence& other) const {
+    if (pattern.size() != other.pattern.size()) {
+        return false;
+    }
+    for (size_t i = 0; i < pattern.size(); i++) {
+        if (pattern[i] != other.pattern[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void VibrationSequence::addElement(VibrationElement element) {
+    pattern.push_back(element);
+}
+
+const std::string VibrationSequence::toString() const {
+    std::string dump;
+    dump += "[";
+
+    for (const auto& element : pattern) {
+        dump += element.toString();
+        dump += " ";
+    }
+
+    dump += "]";
+    return dump;
 }
 
 } // namespace android
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 9abf8b1..bd275a7 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -16,6 +16,7 @@
         "libstatslog",
         "libui",
         "libutils",
+        "lib-platform-compat-native-api",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index ff9aac9..d467692 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -47,6 +47,7 @@
         "libstatslog",
         "libui",
         "libutils",
+        "lib-platform-compat-native-api",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 91e9536..ed4f05a 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -53,6 +53,8 @@
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <com/android/internal/compat/IPlatformCompatNative.h>
 #include <input/InputDevice.h>
 #include <input/InputWindow.h>
 #include <log/log.h>
@@ -79,8 +81,10 @@
 
 using android::base::StringPrintf;
 using android::os::BlockUntrustedTouchesMode;
+using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
+using com::android::internal::compat::IPlatformCompatNative;
 
 namespace android::inputdispatcher {
 
@@ -422,6 +426,15 @@
     return *lhs == *rhs;
 }
 
+static sp<IPlatformCompatNative> getCompatService() {
+    sp<IBinder> service(defaultServiceManager()->getService(String16("platform_compat_native")));
+    if (service == nullptr) {
+        ALOGE("Failed to link to compat service");
+        return nullptr;
+    }
+    return interface_cast<IPlatformCompatNative>(service);
+}
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -440,7 +453,8 @@
         // initialize it here anyways.
         mInTouchMode(true),
         mMaximumObscuringOpacityForTouch(1.0f),
-        mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
+        mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+        mCompatService(getCompatService()) {
     mLooper = new Looper(false);
     mReporter = createInputReporter();
 
@@ -4010,8 +4024,8 @@
         const std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>& handlesPerDisplay) {
     { // acquire lock
         std::scoped_lock _l(mLock);
-        for (auto const& i : handlesPerDisplay) {
-            setInputWindowsLocked(i.second, i.first);
+        for (const auto& [displayId, handles] : handlesPerDisplay) {
+            setInputWindowsLocked(handles, displayId);
         }
     }
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -4113,6 +4127,18 @@
                 ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
             }
             oldWindowHandle->releaseChannel();
+            // To avoid making too many calls into the compat framework, only
+            // check for window flags when windows are going away.
+            // TODO(b/157929241) : delete this. This is only needed temporarily
+            // in order to gather some data about the flag usage
+            if (oldWindowHandle->getInfo()->flags.test(InputWindowInfo::Flag::SLIPPERY)) {
+                ALOGW("%s has FLAG_SLIPPERY. Please report this in b/157929241",
+                      oldWindowHandle->getName().c_str());
+                if (mCompatService != nullptr) {
+                    mCompatService->reportChangeByUid(IInputConstants::BLOCK_FLAG_SLIPPERY,
+                                                      oldWindowHandle->getInfo()->ownerUid);
+                }
+            }
         }
     }
 }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 9aaae74..8f58785 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -32,6 +32,7 @@
 #include "TouchedWindow.h"
 
 #include <attestation/HmacKeyManager.h>
+#include <com/android/internal/compat/IPlatformCompatNative.h>
 #include <input/Input.h>
 #include <input/InputApplication.h>
 #include <input/InputTransport.h>
@@ -596,6 +597,7 @@
     void traceWaitQueueLength(const sp<Connection>& connection);
 
     sp<InputReporterInterface> mReporter;
+    sp<com::android::internal::compat::IPlatformCompatNative> mCompatService;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index ffd8bf2..6cce8ec 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -101,10 +101,14 @@
     virtual void requestRefreshConfiguration(uint32_t changes) = 0;
 
     /* Controls the vibrator of a particular input device. */
-    virtual void vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern,
-                         ssize_t repeat, int32_t token) = 0;
+    virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
+                         int32_t token) = 0;
     virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
 
+    virtual bool isVibrating(int32_t deviceId) = 0;
+
+    virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
+
     /* Return true if the device can send input events to the specified display. */
     virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
 };
diff --git a/services/inputflinger/include/VibrationElement.h b/services/inputflinger/include/VibrationElement.h
index b60ffac..736041e 100644
--- a/services/inputflinger/include/VibrationElement.h
+++ b/services/inputflinger/include/VibrationElement.h
@@ -21,6 +21,7 @@
 #include <chrono>
 #include <cstdint>
 #include <string>
+#include <vector>
 
 namespace android {
 
@@ -32,13 +33,43 @@
 struct VibrationElement {
     std::chrono::milliseconds duration;
     // Channel amplitude range 0-255.
-    std::array<uint8_t, CHANNEL_SIZE> channels = {0, 0};
+    std::vector<std::pair<int32_t /*vibratorId*/, uint8_t /*amplitude*/>> channels;
+
+    explicit VibrationElement(size_t channelNum);
+
+    VibrationElement(const VibrationElement& other);
+
+    bool operator==(const VibrationElement& other) const;
+
+    bool operator!=(const VibrationElement& other) const;
+
+    void addChannel(int32_t vibratorId, uint8_t amplitude);
 
     const std::string toString() const;
-    uint16_t getMagnitude(size_t channelIndex) const;
+
+    uint16_t getMagnitude(int32_t vibratorId) const;
+
     bool isOn() const;
 };
 
+/*
+ * Describes a sequence of rumble effect
+ */
+struct VibrationSequence {
+    // Pattern of vibration elements
+    std::vector<VibrationElement> pattern;
+
+    explicit VibrationSequence(size_t length);
+
+    void operator=(const VibrationSequence& other);
+
+    bool operator==(const VibrationSequence& other) const;
+
+    void addElement(VibrationElement element);
+
+    const std::string toString() const;
+};
+
 } // namespace android
 
 #endif // _VIBRATION_ELEMENT_H
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index c5210b5..f864c0e 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -61,8 +61,8 @@
 // v4l2 devices go directly into /dev
 static const char* VIDEO_DEVICE_PATH = "/dev";
 
-static constexpr size_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
-static constexpr size_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
+static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
+static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
 
 static inline const char* toString(bool value) {
     return value ? "true" : "false";
@@ -475,25 +475,25 @@
 }
 
 InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     return device != nullptr ? device->identifier : InputDeviceIdentifier();
 }
 
 Flags<InputDeviceClass> EventHub::getDeviceClasses(int32_t deviceId) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     return device != nullptr ? device->classes : Flags<InputDeviceClass>(0);
 }
 
 int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     return device != nullptr ? device->controllerNumber : 0;
 }
 
 void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->configuration) {
         *outConfiguration = *device->configuration;
@@ -507,7 +507,7 @@
     outAxisInfo->clear();
 
     if (axis >= 0 && axis <= ABS_MAX) {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
         if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -534,7 +534,7 @@
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
     if (axis >= 0 && axis <= REL_MAX) {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
         Device* device = getDeviceLocked(deviceId);
         return device != nullptr ? device->relBitmask.test(axis) : false;
     }
@@ -542,7 +542,7 @@
 }
 
 bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     Device* device = getDeviceLocked(deviceId);
     return property >= 0 && property <= INPUT_PROP_MAX && device != nullptr
@@ -552,7 +552,7 @@
 
 int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
     if (scanCode >= 0 && scanCode <= KEY_MAX) {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
         if (device != nullptr && device->hasValidFd() && device->keyBitmask.test(scanCode)) {
@@ -565,7 +565,7 @@
 }
 
 int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
@@ -588,7 +588,7 @@
 
 int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
     if (sw >= 0 && sw <= SW_MAX) {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
         if (device != nullptr && device->hasValidFd() && device->swBitmask.test(sw)) {
@@ -604,7 +604,7 @@
     *outValue = 0;
 
     if (axis >= 0 && axis <= ABS_MAX) {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         Device* device = getDeviceLocked(deviceId);
         if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -624,7 +624,7 @@
 
 bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
                                      uint8_t* outFlags) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -652,7 +652,7 @@
 
 status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
                           int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     status_t status = NAME_NOT_FOUND;
 
@@ -692,7 +692,7 @@
 }
 
 status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
 
     if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -706,13 +706,13 @@
 }
 
 void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     mExcludedDevices = devices;
 }
 
 bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && scanCode >= 0 && scanCode <= KEY_MAX) {
         return device->keyBitmask.test(scanCode);
@@ -721,7 +721,7 @@
 }
 
 bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     int32_t sc;
     if (device != nullptr && device->mapLed(led, &sc) == NO_ERROR) {
@@ -731,7 +731,7 @@
 }
 
 void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->hasValidFd()) {
         device->setLedStateLocked(led, on);
@@ -742,7 +742,7 @@
                                         std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
     outVirtualKeys.clear();
 
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->virtualKeyMap) {
         const std::vector<VirtualKeyDefinition> virtualKeys =
@@ -752,7 +752,7 @@
 }
 
 const std::shared_ptr<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr) {
         return device->getKeyCharacterMap();
@@ -761,7 +761,7 @@
 }
 
 bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && map != nullptr && device->keyMap.keyCharacterMap != nullptr) {
         device->keyMap.keyCharacterMap->combine(*map);
@@ -822,7 +822,7 @@
 }
 
 void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->hasValidFd()) {
         ff_effect effect;
@@ -857,7 +857,7 @@
 }
 
 void EventHub::cancelVibrate(int32_t deviceId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->hasValidFd()) {
         if (device->ffEffectPlaying) {
@@ -878,6 +878,18 @@
     }
 }
 
+std::vector<int32_t> EventHub::getVibratorIds(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+    std::vector<int32_t> vibrators;
+    Device* device = getDeviceLocked(deviceId);
+    if (device != nullptr && device->hasValidFd() &&
+        device->classes.test(InputDeviceClass::VIBRATOR)) {
+        vibrators.push_back(FF_STRONG_MAGNITUDE_CHANNEL_IDX);
+        vibrators.push_back(FF_WEAK_MAGNITUDE_CHANNEL_IDX);
+    }
+    return vibrators;
+}
+
 EventHub::Device* EventHub::getDeviceByDescriptorLocked(const std::string& descriptor) const {
     for (const auto& [id, device] : mDevices) {
         if (descriptor == device->identifier.descriptor) {
@@ -930,7 +942,7 @@
 size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
     ALOG_ASSERT(bufferSize >= 1);
 
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     struct input_event readBuffer[bufferSize];
 
@@ -1184,7 +1196,7 @@
 }
 
 std::vector<TouchVideoFrame> EventHub::getVideoFrames(int32_t deviceId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->videoDevice) {
@@ -1593,7 +1605,7 @@
 }
 
 bool EventHub::isDeviceEnabled(int32_t deviceId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
         ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1603,7 +1615,7 @@
 }
 
 status_t EventHub::enableDevice(int32_t deviceId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
         ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1625,7 +1637,7 @@
 }
 
 status_t EventHub::disableDevice(int32_t deviceId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
         ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1809,7 +1821,7 @@
 void EventHub::requestReopenDevices() {
     ALOGV("requestReopenDevices() called");
 
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     mNeedToReopenDevices = true;
 }
 
@@ -1817,7 +1829,7 @@
     dump += "Event Hub State:\n";
 
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         dump += StringPrintf(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
 
@@ -1868,8 +1880,7 @@
 
 void EventHub::monitor() {
     // Acquire and release the lock to ensure that the event hub has not deadlocked.
-    mLock.lock();
-    mLock.unlock();
+    std::unique_lock<std::mutex> lock(mLock);
 }
 
 }; // namespace android
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 271bc2f..d25d64a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -429,10 +429,9 @@
     return result;
 }
 
-void InputDevice::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
-                          int32_t token) {
-    for_each_mapper([pattern, repeat, token](InputMapper& mapper) {
-        mapper.vibrate(pattern, repeat, token);
+void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {
+    for_each_mapper([sequence, repeat, token](InputMapper& mapper) {
+        mapper.vibrate(sequence, repeat, token);
     });
 }
 
@@ -440,6 +439,27 @@
     for_each_mapper([token](InputMapper& mapper) { mapper.cancelVibrate(token); });
 }
 
+bool InputDevice::isVibrating() {
+    bool vibrating = false;
+    for_each_mapper([&vibrating](InputMapper& mapper) { vibrating |= mapper.isVibrating(); });
+    return vibrating;
+}
+
+/* There's no guarantee the IDs provided by the different mappers are unique, so if we have two
+ * different vibration mappers then we could have duplicate IDs.
+ * Alternatively, if we have a merged device that has multiple evdev nodes with FF_* capabilities,
+ * we would definitely have duplicate IDs.
+ */
+std::vector<int32_t> InputDevice::getVibratorIds() {
+    std::vector<int32_t> vibrators;
+    for_each_mapper([&vibrators](InputMapper& mapper) {
+        std::vector<int32_t> devVibs = mapper.getVibratorIds();
+        vibrators.reserve(vibrators.size() + devVibs.size());
+        vibrators.insert(vibrators.end(), devVibs.begin(), devVibs.end());
+    });
+    return vibrators;
+}
+
 void InputDevice::cancelTouch(nsecs_t when) {
     for_each_mapper([when](InputMapper& mapper) { mapper.cancelTouch(when); });
 }
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index e263f01..a6b5e2d 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -56,7 +56,7 @@
     mQueuedListener = new QueuedInputListener(listener);
 
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         refreshConfigurationLocked(0);
         updateGlobalMetaStateLocked();
@@ -87,8 +87,9 @@
     int32_t oldGeneration;
     int32_t timeoutMillis;
     bool inputDevicesChanged = false;
+    std::vector<InputDeviceInfo> inputDevices;
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         oldGeneration = mGeneration;
         timeoutMillis = -1;
@@ -107,8 +108,8 @@
     size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
 
     { // acquire lock
-        AutoMutex _l(mLock);
-        mReaderIsAliveCondition.broadcast();
+        std::scoped_lock _l(mLock);
+        mReaderIsAliveCondition.notify_all();
 
         if (count) {
             processEventsLocked(mEventBuffer, count);
@@ -127,12 +128,13 @@
 
         if (oldGeneration != mGeneration) {
             inputDevicesChanged = true;
+            inputDevices = getInputDevicesLocked();
         }
     } // release lock
 
     // Send out a message that the describes the changed input devices.
     if (inputDevicesChanged) {
-        mPolicy->notifyInputDevicesChanged(getInputDevicesLocked());
+        mPolicy->notifyInputDevicesChanged(inputDevices);
     }
 
     // Flush queued events out to the listener.
@@ -216,7 +218,7 @@
     bumpGenerationLocked();
 
     if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
-        notifyExternalStylusPresenceChanged();
+        notifyExternalStylusPresenceChangedLocked();
     }
 }
 
@@ -256,7 +258,7 @@
     device->removeEventHubDevice(eventHubId);
 
     if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
-        notifyExternalStylusPresenceChanged();
+        notifyExternalStylusPresenceChangedLocked();
     }
 
     if (device->hasEventHubDevices()) {
@@ -301,7 +303,7 @@
     device->process(rawEvents, count);
 }
 
-InputDevice* InputReader::findInputDevice(int32_t deviceId) {
+InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) {
     auto deviceIt =
             std::find_if(mDevices.begin(), mDevices.end(), [deviceId](const auto& devicePair) {
                 return devicePair.second->getId() == deviceId;
@@ -389,7 +391,7 @@
     return mLedMetaState;
 }
 
-void InputReader::notifyExternalStylusPresenceChanged() {
+void InputReader::notifyExternalStylusPresenceChangedLocked() {
     refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
 }
 
@@ -405,6 +407,7 @@
 }
 
 void InputReader::dispatchExternalStylusState(const StylusState& state) {
+    std::scoped_lock _l(mLock);
     for (auto& devicePair : mDevices) {
         std::shared_ptr<InputDevice>& device = devicePair.second;
         device->updateExternalStylusState(state);
@@ -479,7 +482,7 @@
 }
 
 std::vector<InputDeviceInfo> InputReader::getInputDevices() const {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     return getInputDevicesLocked();
 }
 
@@ -498,19 +501,19 @@
 }
 
 int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
 }
 
 int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
 }
 
 int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
 }
@@ -519,7 +522,7 @@
                                     GetStateFunc getStateFunc) {
     int32_t result = AKEY_STATE_UNKNOWN;
     if (deviceId >= 0) {
-        InputDevice* device = findInputDevice(deviceId);
+        InputDevice* device = findInputDeviceLocked(deviceId);
         if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
             result = (device->*getStateFunc)(sourceMask, code);
         }
@@ -542,7 +545,8 @@
 }
 
 void InputReader::toggleCapsLockState(int32_t deviceId) {
-    InputDevice* device = findInputDevice(deviceId);
+    std::scoped_lock _l(mLock);
+    InputDevice* device = findInputDeviceLocked(deviceId);
     if (!device) {
         ALOGW("Ignoring toggleCapsLock for unknown deviceId %" PRId32 ".", deviceId);
         return;
@@ -557,7 +561,7 @@
 
 bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
                           const int32_t* keyCodes, uint8_t* outFlags) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     memset(outFlags, 0, numCodes);
     return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
@@ -568,7 +572,7 @@
                                               uint8_t* outFlags) {
     bool result = false;
     if (deviceId >= 0) {
-        InputDevice* device = findInputDevice(deviceId);
+        InputDevice* device = findInputDeviceLocked(deviceId);
         if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
             result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
         }
@@ -584,7 +588,7 @@
 }
 
 void InputReader::requestRefreshConfiguration(uint32_t changes) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     if (changes) {
         bool needWake = !mConfigurationChangesToRefresh;
@@ -596,28 +600,49 @@
     }
 }
 
-void InputReader::vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern,
-                          ssize_t repeat, int32_t token) {
-    AutoMutex _l(mLock);
-    InputDevice* device = findInputDevice(deviceId);
+void InputReader::vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
+                          int32_t token) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
     if (device) {
-        device->vibrate(pattern, repeat, token);
+        device->vibrate(sequence, repeat, token);
     }
 }
 
 void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
-    InputDevice* device = findInputDevice(deviceId);
+    InputDevice* device = findInputDeviceLocked(deviceId);
     if (device) {
         device->cancelVibrate(token);
     }
 }
 
-bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
-    AutoMutex _l(mLock);
+bool InputReader::isVibrating(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
 
-    InputDevice* device = findInputDevice(deviceId);
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->isVibrating();
+    }
+    return false;
+}
+
+std::vector<int32_t> InputReader::getVibratorIds(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getVibratorIds();
+    }
+    return {};
+}
+
+bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
     if (device) {
         return device->isEnabled();
     }
@@ -626,9 +651,9 @@
 }
 
 bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
-    InputDevice* device = findInputDevice(deviceId);
+    InputDevice* device = findInputDeviceLocked(deviceId);
     if (!device) {
         ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
         return false;
@@ -654,7 +679,7 @@
 }
 
 void InputReader::dump(std::string& dump) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     mEventHub->dump(dump);
     dump += "\n";
@@ -729,11 +754,9 @@
 
 void InputReader::monitor() {
     // Acquire and release the lock to ensure that the reader has not deadlocked.
-    mLock.lock();
+    std::unique_lock<std::mutex> lock(mLock);
     mEventHub->wake();
-    mReaderIsAliveCondition.wait(mLock);
-    mLock.unlock();
-
+    mReaderIsAliveCondition.wait(lock);
     // Check the EventHub
     mEventHub->monitor();
 }
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index edb82d3..9e38d0a 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -233,6 +233,7 @@
     /* Control the vibrator. */
     virtual void vibrate(int32_t deviceId, const VibrationElement& effect) = 0;
     virtual void cancelVibrate(int32_t deviceId) = 0;
+    virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
 
     /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
     virtual void requestReopenDevices() = 0;
@@ -381,6 +382,7 @@
 
     void vibrate(int32_t deviceId, const VibrationElement& effect) override final;
     void cancelVibrate(int32_t deviceId) override final;
+    std::vector<int32_t> getVibratorIds(int32_t deviceId) override final;
 
     void requestReopenDevices() override final;
 
@@ -502,7 +504,7 @@
     void releaseControllerNumberLocked(int32_t num);
 
     // Protect all internal state.
-    mutable Mutex mLock;
+    mutable std::mutex mLock;
 
     // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
     // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 7d160eb..8b14b06 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -82,8 +82,10 @@
     int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
     bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags);
-    void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat, int32_t token);
+    void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
     void cancelVibrate(int32_t token);
+    bool isVibrating();
+    std::vector<int32_t> getVibratorIds();
     void cancelTouch(nsecs_t when);
 
     int32_t getMetaState();
@@ -272,6 +274,8 @@
     }
     inline void cancelVibrate() { return mEventHub->cancelVibrate(mId); }
 
+    inline std::vector<int32_t> getVibratorIds() { return mEventHub->getVibratorIds(mId); }
+
     inline bool hasAbsoluteAxis(int32_t code) const {
         RawAbsoluteAxisInfo info;
         mEventHub->getAbsoluteAxisInfo(mId, code, &info);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 563018a..b16b86c 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -18,6 +18,7 @@
 #define _UI_INPUTREADER_INPUT_READER_H
 
 #include <PointerControllerInterface.h>
+#include <android-base/thread_annotations.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
 
@@ -76,16 +77,21 @@
 
     void requestRefreshConfiguration(uint32_t changes) override;
 
-    void vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern, ssize_t repeat,
+    void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
                  int32_t token) override;
     void cancelVibrate(int32_t deviceId, int32_t token) override;
 
+    bool isVibrating(int32_t deviceId) override;
+
+    std::vector<int32_t> getVibratorIds(int32_t deviceId) override;
+
     bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) override;
 
 protected:
     // These members are protected so they can be instrumented by test cases.
-    virtual std::shared_ptr<InputDevice> createDeviceLocked(
-            int32_t deviceId, const InputDeviceIdentifier& identifier);
+    virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
+                                                            const InputDeviceIdentifier& identifier)
+            REQUIRES(mLock);
 
     // With each iteration of the loop, InputReader reads and processes one incoming message from
     // the EventHub.
@@ -97,33 +103,37 @@
 
     public:
         explicit ContextImpl(InputReader* reader);
-
-        void updateGlobalMetaState() override;
-        int32_t getGlobalMetaState() override;
-        void disableVirtualKeysUntil(nsecs_t time) override;
-        bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override;
-        void fadePointer() override;
-        std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override;
-        void requestTimeoutAtTime(nsecs_t when) override;
-        int32_t bumpGeneration() override;
-        void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override;
-        void dispatchExternalStylusState(const StylusState& outState) override;
-        InputReaderPolicyInterface* getPolicy() override;
-        InputListenerInterface* getListener() override;
-        EventHubInterface* getEventHub() override;
-        int32_t getNextId() override;
-        void updateLedMetaState(int32_t metaState) override;
-        int32_t getLedMetaState() override;
+        // lock is already held by the input loop
+        void updateGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+        int32_t getGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+        void disableVirtualKeysUntil(nsecs_t time) NO_THREAD_SAFETY_ANALYSIS override;
+        bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode,
+                                  int32_t scanCode) NO_THREAD_SAFETY_ANALYSIS override;
+        void fadePointer() NO_THREAD_SAFETY_ANALYSIS override;
+        std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
+                NO_THREAD_SAFETY_ANALYSIS override;
+        void requestTimeoutAtTime(nsecs_t when) NO_THREAD_SAFETY_ANALYSIS override;
+        int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
+        void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
+                NO_THREAD_SAFETY_ANALYSIS override;
+        void dispatchExternalStylusState(const StylusState& outState)
+                NO_THREAD_SAFETY_ANALYSIS override;
+        InputReaderPolicyInterface* getPolicy() NO_THREAD_SAFETY_ANALYSIS override;
+        InputListenerInterface* getListener() NO_THREAD_SAFETY_ANALYSIS override;
+        EventHubInterface* getEventHub() NO_THREAD_SAFETY_ANALYSIS override;
+        int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override;
+        void updateLedMetaState(int32_t metaState) NO_THREAD_SAFETY_ANALYSIS override;
+        int32_t getLedMetaState() NO_THREAD_SAFETY_ANALYSIS override;
     } mContext;
 
     friend class ContextImpl;
+    // Test cases need to override the locked functions
+    mutable std::mutex mLock;
 
 private:
     std::unique_ptr<InputThread> mThread;
 
-    mutable Mutex mLock;
-
-    Condition mReaderIsAliveCondition;
+    std::condition_variable mReaderIsAliveCondition;
 
     // This could be unique_ptr, but due to the way InputReader tests are written,
     // it is made shared_ptr here. In the tests, an EventHub reference is retained by the test
@@ -132,76 +142,79 @@
     sp<InputReaderPolicyInterface> mPolicy;
     sp<QueuedInputListener> mQueuedListener;
 
-    InputReaderConfiguration mConfig;
+    InputReaderConfiguration mConfig GUARDED_BY(mLock);
 
     // The event queue.
     static const int EVENT_BUFFER_SIZE = 256;
-    RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+    RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);
 
     // An input device can represent a collection of EventHub devices. This map provides a way
     // to lookup the input device instance from the EventHub device id.
-    std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices;
+    std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices
+            GUARDED_BY(mLock);
 
     // An input device contains one or more eventHubId, this map provides a way to lookup the
     // EventHubIds contained in the input device from the input device instance.
     std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
-            mDeviceToEventHubIdsMap;
+            mDeviceToEventHubIdsMap GUARDED_BY(mLock);
 
     // low-level input event decoding and device management
-    void processEventsLocked(const RawEvent* rawEvents, size_t count);
+    void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock);
 
-    void addDeviceLocked(nsecs_t when, int32_t eventHubId);
-    void removeDeviceLocked(nsecs_t when, int32_t eventHubId);
-    void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count);
-    void timeoutExpiredLocked(nsecs_t when);
+    void addDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+    void removeDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+    void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count)
+            REQUIRES(mLock);
+    void timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock);
 
-    void handleConfigurationChangedLocked(nsecs_t when);
+    void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock);
 
-    int32_t mGlobalMetaState;
-    void updateGlobalMetaStateLocked();
-    int32_t getGlobalMetaStateLocked();
+    int32_t mGlobalMetaState GUARDED_BY(mLock);
+    void updateGlobalMetaStateLocked() REQUIRES(mLock);
+    int32_t getGlobalMetaStateLocked() REQUIRES(mLock);
 
-    int32_t mLedMetaState;
-    void updateLedMetaStateLocked(int32_t metaState);
-    int32_t getLedMetaStateLocked();
+    int32_t mLedMetaState GUARDED_BY(mLock);
+    void updateLedMetaStateLocked(int32_t metaState) REQUIRES(mLock);
+    int32_t getLedMetaStateLocked() REQUIRES(mLock);
 
-    void notifyExternalStylusPresenceChanged();
-    void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices);
+    void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock);
+    void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) REQUIRES(mLock);
     void dispatchExternalStylusState(const StylusState& state);
 
     // The PointerController that is shared among all the input devices that need it.
     std::weak_ptr<PointerControllerInterface> mPointerController;
-    std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId);
-    void updatePointerDisplayLocked();
-    void fadePointerLocked();
+    std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
+            REQUIRES(mLock);
+    void updatePointerDisplayLocked() REQUIRES(mLock);
+    void fadePointerLocked() REQUIRES(mLock);
 
-    int32_t mGeneration;
-    int32_t bumpGenerationLocked();
+    int32_t mGeneration GUARDED_BY(mLock);
+    int32_t bumpGenerationLocked() REQUIRES(mLock);
 
-    int32_t mNextInputDeviceId;
-    int32_t nextInputDeviceIdLocked();
+    int32_t mNextInputDeviceId GUARDED_BY(mLock);
+    int32_t nextInputDeviceIdLocked() REQUIRES(mLock);
 
-    std::vector<InputDeviceInfo> getInputDevicesLocked() const;
+    std::vector<InputDeviceInfo> getInputDevicesLocked() const REQUIRES(mLock);
 
-    nsecs_t mDisableVirtualKeysTimeout;
-    void disableVirtualKeysUntilLocked(nsecs_t time);
-    bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode);
+    nsecs_t mDisableVirtualKeysTimeout GUARDED_BY(mLock);
+    void disableVirtualKeysUntilLocked(nsecs_t time) REQUIRES(mLock);
+    bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode) REQUIRES(mLock);
 
-    nsecs_t mNextTimeout;
-    void requestTimeoutAtTimeLocked(nsecs_t when);
+    nsecs_t mNextTimeout GUARDED_BY(mLock);
+    void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock);
 
-    uint32_t mConfigurationChangesToRefresh;
-    void refreshConfigurationLocked(uint32_t changes);
+    uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock);
+    void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock);
 
     // state queries
     typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
-                           GetStateFunc getStateFunc);
+                           GetStateFunc getStateFunc) REQUIRES(mLock);
     bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
-                                     const int32_t* keyCodes, uint8_t* outFlags);
+                                     const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock);
 
     // find an InputDevice from an InputDevice id
-    InputDevice* findInputDevice(int32_t deviceId);
+    InputDevice* findInputDeviceLocked(int32_t deviceId) REQUIRES(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 1db829f..913cef7 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -56,11 +56,18 @@
     return false;
 }
 
-void InputMapper::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
-                          int32_t token) {}
+void InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {}
 
 void InputMapper::cancelVibrate(int32_t token) {}
 
+bool InputMapper::isVibrating() {
+    return false;
+}
+
+std::vector<int32_t> InputMapper::getVibratorIds() {
+    return {};
+}
+
 void InputMapper::cancelTouch(nsecs_t when) {}
 
 int32_t InputMapper::getMetaState() {
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 56ab928..088dbd8 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -63,9 +63,10 @@
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
     virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                        const int32_t* keyCodes, uint8_t* outFlags);
-    virtual void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
-                         int32_t token);
+    virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
     virtual void cancelVibrate(int32_t token);
+    virtual bool isVibrating();
+    virtual std::vector<int32_t> getVibratorIds();
     virtual void cancelTouch(nsecs_t when);
 
     virtual int32_t getMetaState();
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index ac7c266..f25e59a 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -21,7 +21,7 @@
 namespace android {
 
 VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mVibrating(false) {}
+      : InputMapper(deviceContext), mVibrating(false), mSequence(0) {}
 
 VibratorInputMapper::~VibratorInputMapper() {}
 
@@ -39,17 +39,15 @@
     // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
 }
 
-void VibratorInputMapper::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
+void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat,
                                   int32_t token) {
 #if DEBUG_VIBRATOR
-    std::string patternStr;
-    dumpPattern(patternStr);
     ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
-          patternStr.c_str(), repeat, token);
+          sequence.toString().c_str(), repeat, token);
 #endif
 
     mVibrating = true;
-    mPattern = pattern;
+    mSequence = sequence;
     mRepeat = repeat;
     mToken = token;
     mIndex = -1;
@@ -67,6 +65,14 @@
     }
 }
 
+bool VibratorInputMapper::isVibrating() {
+    return mVibrating;
+}
+
+std::vector<int32_t> VibratorInputMapper::getVibratorIds() {
+    return getDeviceContext().getVibratorIds();
+}
+
 void VibratorInputMapper::timeoutExpired(nsecs_t when) {
     if (mVibrating) {
         if (when >= mNextStepTime) {
@@ -79,7 +85,7 @@
 
 void VibratorInputMapper::nextStep() {
     mIndex += 1;
-    if (size_t(mIndex) >= mPattern.size()) {
+    if (size_t(mIndex) >= mSequence.pattern.size()) {
         if (mRepeat < 0) {
             // We are done.
             stopVibrating();
@@ -88,7 +94,7 @@
         mIndex = mRepeat;
     }
 
-    const VibrationElement& element = mPattern[mIndex];
+    const VibrationElement& element = mSequence.pattern[mIndex];
     if (element.isOn()) {
 #if DEBUG_VIBRATOR
         std::string description = element.toString();
@@ -125,23 +131,10 @@
     dump += StringPrintf(INDENT3 "Vibrating: %s\n", toString(mVibrating));
     if (mVibrating) {
         dump += INDENT3 "Pattern: ";
-        dumpPattern(dump);
+        dump += mSequence.toString();
         dump += "\n";
         dump += StringPrintf(INDENT3 "Repeat Index: %zd\n", mRepeat);
     }
 }
 
-void VibratorInputMapper::dumpPattern(std::string& dump) const {
-    dump += "[";
-
-    for (auto it = mPattern.begin(); it != mPattern.end(); ++it) {
-        dump += it->toString();
-        if (std::next(it) != mPattern.end()) {
-            dump += ", ";
-        }
-    }
-
-    dump += "]";
-}
-
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index bfa5ec1..7ce621a 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -30,21 +30,21 @@
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void process(const RawEvent* rawEvent) override;
 
-    virtual void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
-                         int32_t token) override;
+    virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) override;
     virtual void cancelVibrate(int32_t token) override;
+    virtual bool isVibrating() override;
+    virtual std::vector<int32_t> getVibratorIds() override;
     virtual void timeoutExpired(nsecs_t when) override;
     virtual void dump(std::string& dump) override;
 
 private:
     bool mVibrating;
-    std::vector<VibrationElement> mPattern;
+    VibrationSequence mSequence;
     ssize_t mRepeat;
     int32_t mToken;
     ssize_t mIndex;
     nsecs_t mNextStepTime;
 
-    void dumpPattern(std::string& dump) const;
     void nextStep();
     void stopVibrating();
 };
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 99eaac6..cd61750 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -27,6 +27,7 @@
 #include <TestInputListener.h>
 #include <TouchInputMapper.h>
 #include <UinputDevice.h>
+#include <VibratorInputMapper.h>
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <inttypes.h>
@@ -396,6 +397,7 @@
     std::vector<std::string> mExcludedDevices;
     List<RawEvent> mEvents GUARDED_BY(mLock);
     std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
+    std::vector<int32_t> mVibrators = {0, 1};
 
 public:
     virtual ~FakeEventHub() {
@@ -810,6 +812,8 @@
 
     void cancelVibrate(int32_t) override {}
 
+    std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; };
+
     virtual bool isExternal(int32_t) const {
         return false;
     }
@@ -1030,8 +1034,9 @@
     using InputReader::loopOnce;
 
 protected:
-    virtual std::shared_ptr<InputDevice> createDeviceLocked(
-            int32_t eventHubId, const InputDeviceIdentifier& identifier) {
+    virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
+                                                            const InputDeviceIdentifier& identifier)
+            REQUIRES(mLock) {
         if (!mNextDevices.empty()) {
             std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
             mNextDevices.pop();
@@ -1815,6 +1820,31 @@
     ASSERT_FALSE(args.enabled) << "Pointer Capture should be disabled.";
 }
 
+class FakeVibratorInputMapper : public FakeInputMapper {
+public:
+    FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
+          : FakeInputMapper(deviceContext, sources) {}
+
+    std::vector<int32_t> getVibratorIds() override { return getDeviceContext().getVibratorIds(); }
+};
+
+TEST_F(InputReaderTest, VibratorGetVibratorIds) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR;
+    constexpr int32_t eventHubId = 1;
+    const char* DEVICE_LOCATION = "BLUETOOTH";
+    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+    FakeVibratorInputMapper& mapper =
+            device->addMapper<FakeVibratorInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+    mReader->pushNextDevice(device);
+
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+    ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
+
+    ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
+    ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U);
+}
+
 // --- InputReaderIntegrationTest ---
 
 // These tests create and interact with the InputReader only through its interface.
@@ -2495,6 +2525,46 @@
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 }
 
+// --- VibratorInputMapperTest ---
+class VibratorInputMapperTest : public InputMapperTest {
+protected:
+    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::VIBRATOR); }
+};
+
+TEST_F(VibratorInputMapperTest, GetSources) {
+    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
+}
+
+TEST_F(VibratorInputMapperTest, GetVibratorIds) {
+    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+    ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
+}
+
+TEST_F(VibratorInputMapperTest, Vibrate) {
+    constexpr uint8_t DEFAULT_AMPLITUDE = 192;
+    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+    VibrationElement pattern(2);
+    VibrationSequence sequence(2);
+    pattern.duration = std::chrono::milliseconds(200);
+    pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2},
+                        {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+    sequence.addElement(pattern);
+    pattern.duration = std::chrono::milliseconds(500);
+    pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4},
+                        {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+    sequence.addElement(pattern);
+
+    std::vector<int64_t> timings = {0, 1};
+    std::vector<uint8_t> amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2};
+
+    ASSERT_FALSE(mapper.isVibrating());
+    mapper.vibrate(sequence, -1 /* repeat */, 0 /* token */);
+    ASSERT_TRUE(mapper.isVibrating());
+}
 
 // --- KeyboardInputMapperTest ---
 
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 4efff75..5cf0460 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -65,10 +65,70 @@
     }
 }
 
+status_t BufferStateLayer::addReleaseFence(const sp<CallbackHandle>& ch,
+                                           const sp<Fence>& fence) {
+    if (ch == nullptr) {
+        return OK;
+    }
+    if (!ch->previousReleaseFence.get()) {
+        ch->previousReleaseFence = fence;
+        return OK;
+    }
+
+    // Below logic is lifted from ConsumerBase.cpp:
+    // Check status of fences first because merging is expensive.
+    // Merging an invalid fence with any other fence results in an
+    // invalid fence.
+    auto currentStatus = ch->previousReleaseFence->getStatus();
+    if (currentStatus == Fence::Status::Invalid) {
+        ALOGE("Existing fence has invalid state, layer: %s", mName.c_str());
+        return BAD_VALUE;
+    }
+
+    auto incomingStatus = fence->getStatus();
+    if (incomingStatus == Fence::Status::Invalid) {
+        ALOGE("New fence has invalid state, layer: %s", mName.c_str());
+        ch->previousReleaseFence = fence;
+        return BAD_VALUE;
+    }
+
+    // If both fences are signaled or both are unsignaled, we need to merge
+    // them to get an accurate timestamp.
+    if (currentStatus == incomingStatus) {
+        char fenceName[32] = {};
+        snprintf(fenceName, 32, "%.28s", mName.c_str());
+        sp<Fence> mergedFence = Fence::merge(
+                fenceName, ch->previousReleaseFence, fence);
+        if (!mergedFence.get()) {
+            ALOGE("failed to merge release fences, layer: %s", mName.c_str());
+            // synchronization is broken, the best we can do is hope fences
+            // signal in order so the new fence will act like a union
+            ch->previousReleaseFence = fence;
+            return BAD_VALUE;
+        }
+        ch->previousReleaseFence = mergedFence;
+    } else if (incomingStatus == Fence::Status::Unsignaled) {
+        // If one fence has signaled and the other hasn't, the unsignaled
+        // fence will approximately correspond with the correct timestamp.
+        // There's a small race if both fences signal at about the same time
+        // and their statuses are retrieved with unfortunate timing. However,
+        // by this point, they will have both signaled and only the timestamp
+        // will be slightly off; any dependencies after this point will
+        // already have been met.
+        ch->previousReleaseFence = fence;
+    }
+    // else if (currentStatus == Fence::Status::Unsignaled) is a no-op.
+
+    return OK;
+}
+
 // -----------------------------------------------------------------------
 // Interface implementation for Layer
 // -----------------------------------------------------------------------
 void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
+    if (!releaseFence->isValid()) {
+        return;
+    }
     // The previous release fence notifies the client that SurfaceFlinger is done with the previous
     // buffer that was presented on this layer. The first transaction that came in this frame that
     // replaced the previous buffer on this layer needs this release fence, because the fence will
@@ -85,12 +145,17 @@
     // buffer. It replaces the buffer in the second transaction. The buffer in the second
     // transaction will now no longer be presented so it is released immediately and the third
     // transaction doesn't need a previous release fence.
+    sp<CallbackHandle> ch;
     for (auto& handle : mDrawingState.callbackHandles) {
         if (handle->releasePreviousBuffer) {
-            handle->previousReleaseFence = releaseFence;
+            ch = handle;
             break;
         }
     }
+    auto status = addReleaseFence(ch, releaseFence);
+    if (status != OK) {
+        ALOGE("Failed to add release fence for layer %s", getName().c_str());
+    }
 
     mPreviousReleaseFence = releaseFence;
 
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 0f0b48a..69b27e4 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -123,6 +123,8 @@
     bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
                                  nsecs_t requestedPresentTime);
 
+    status_t addReleaseFence(const sp<CallbackHandle>& ch, const sp<Fence>& releaseFence);
+
     uint64_t getFrameNumber(nsecs_t expectedPresentTime) const override;
 
     bool latchSidebandStream(bool& recomputeVisibleRegions) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 5c7f12d..26f7f68 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -108,6 +108,9 @@
         // If set to true, change the layer settings to render a clear output.
         // This may be requested by the HWC
         const bool clearContent;
+
+        // If set to true, change the layer settings to not use any blurs.
+        const bool disableBlurs;
     };
 
     // A superset of LayerSettings required by RenderEngine to compose a layer
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3852f45..43792dc 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -669,8 +669,14 @@
 compositionengine::OutputLayer* Output::findLayerRequestingBackgroundComposition() const {
     compositionengine::OutputLayer* layerRequestingBgComposition = nullptr;
     for (auto* layer : getOutputLayersOrderedByZ()) {
-        if (layer->getLayerFE().getCompositionState()->backgroundBlurRadius > 0 ||
-            layer->getLayerFE().getCompositionState()->blurRegions.size() > 0) {
+        auto* compState = layer->getLayerFE().getCompositionState();
+
+        // If any layer has a sideband stream, we will disable blurs. In that case, we don't
+        // want to force client composition because of the blur.
+        if (compState->sidebandStream != nullptr) {
+            return nullptr;
+        }
+        if (compState->backgroundBlurRadius > 0 || compState->blurRegions.size() > 0) {
             layerRequestingBgComposition = layer;
         }
     }
@@ -1022,6 +1028,8 @@
     // Used when a layer clears part of the buffer.
     Region stubRegion;
 
+    bool disableBlurs = false;
+
     for (auto* layer : getOutputLayersOrderedByZ()) {
         const auto& layerState = layer->getState();
         const auto* layerFEState = layer->getLayerFE().getCompositionState();
@@ -1035,6 +1043,8 @@
             continue;
         }
 
+        disableBlurs |= layerFEState->sidebandStream != nullptr;
+
         const bool clientComposition = layer->requiresClientComposition();
 
         // We clear the client target for non-client composed layers if
@@ -1063,7 +1073,8 @@
                                    .viewport = outputState.layerStackSpace.content,
                                    .dataspace = outputDataspace,
                                    .realContentIsVisible = realContentIsVisible,
-                                   .clearContent = !clientComposition};
+                                   .clearContent = !clientComposition,
+                                   .disableBlurs = disableBlurs};
             std::vector<LayerFE::LayerSettings> results =
                     layerFE.prepareClientCompositionList(targetSettings);
             if (realContentIsVisible && !results.empty()) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 9badb99..1452192 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3688,6 +3688,7 @@
             kDisplayDataspace,
             false /* realContentIsVisible */,
             true /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3699,6 +3700,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
@@ -3742,6 +3744,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(Rect(0, 0, 30, 30)),
@@ -3753,6 +3756,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(Rect(0, 0, 40, 201)),
@@ -3764,6 +3768,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3795,6 +3800,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3806,6 +3812,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3817,6 +3824,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3848,6 +3856,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
 
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
@@ -3860,6 +3869,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3871,6 +3881,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3901,6 +3912,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3912,6 +3924,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3923,6 +3936,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3951,6 +3965,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3962,6 +3977,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3973,6 +3989,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4092,6 +4109,7 @@
             kOutputDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4109,6 +4127,7 @@
             kOutputDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4142,6 +4161,7 @@
             kDisplayDataspace,
             false /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     LayerFE::LayerSettings mShadowSettings;
@@ -4187,6 +4207,7 @@
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
+            false /* disabledBlurs */,
     };
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 247ee23..2ac67cb 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -72,6 +72,7 @@
         mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
         mProducerSlotSource(0),
         mProducerBuffers(),
+        mProducerSlotNeedReallocation(0),
         mQueueBufferOutput(),
         mSinkBufferWidth(0),
         mSinkBufferHeight(0),
@@ -337,10 +338,14 @@
             dbgSourceStr(source), *sslot, pslot, result);
     uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
 
+    // reset producer slot reallocation flag
+    mProducerSlotNeedReallocation &= ~(1ULL << pslot);
+
     if ((mProducerSlotSource & (1ULL << pslot)) != sourceBit) {
         // This slot was previously dequeued from the other source; must
         // re-request the buffer.
-        result |= BUFFER_NEEDS_REALLOCATION;
+        mProducerSlotNeedReallocation |= 1ULL << pslot;
+
         mProducerSlotSource &= ~(1ULL << pslot);
         mProducerSlotSource |= sourceBit;
     }
@@ -362,6 +367,9 @@
                 dbgSourceStr(source), pslot, mProducerBuffers[pslot].get(),
                 mProducerBuffers[pslot]->getPixelFormat(),
                 mProducerBuffers[pslot]->getUsage());
+
+        // propagate reallocation to VDS consumer
+        mProducerSlotNeedReallocation |= 1ULL << pslot;
     }
 
     return result;
@@ -436,6 +444,11 @@
     if (outBufferAge) {
         *outBufferAge = 0;
     }
+
+    if ((mProducerSlotNeedReallocation & (1ULL << *pslot)) != 0) {
+        result |= BUFFER_NEEDS_REALLOCATION;
+    }
+
     return result;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 1974625..fba0e3b 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -176,6 +176,10 @@
     uint64_t mProducerSlotSource;
     sp<GraphicBuffer> mProducerBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
 
+    // Need to propagate reallocation to VDS consumer.
+    // Each bit corresponds to a producer slot.
+    uint64_t mProducerSlotNeedReallocation;
+
     // The QueueBufferOutput with the latest info from the sink, and with the
     // transform hint cleared. Since we defer queueBuffer from the GPU driver
     // to the sink, we have to return the previous version.
diff --git a/services/surfaceflinger/Fps.h b/services/surfaceflinger/Fps.h
new file mode 100644
index 0000000..38a9af0
--- /dev/null
+++ b/services/surfaceflinger/Fps.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <ostream>
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// Value which represents "frames per second". This class is a wrapper around
+// float, providing some useful utilities, such as comparisons with tolerance
+// and converting between period duruation and frequency.
+class Fps {
+public:
+    static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); }
+
+    Fps() = default;
+    explicit constexpr Fps(float fps)
+          : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {}
+
+    constexpr float getValue() const { return fps; }
+
+    constexpr nsecs_t getPeriodNsecs() const { return period; }
+
+    bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; }
+
+    // DO NOT use for std::sort. Instead use comparesLess().
+    bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; }
+
+    bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; }
+
+    bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); }
+
+    bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); }
+
+    bool isValid() const { return fps > 0.0f; }
+
+    int getIntValue() const { return static_cast<int>(std::round(fps)); }
+
+    // Use this comparator for sorting. Using a comparator with margins can
+    // cause std::sort to crash.
+    inline static bool comparesLess(const Fps& left, const Fps& right) {
+        return left.fps < right.fps;
+    }
+
+    // Compares two FPS with margin.
+    // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c.
+    // DO NOT use with hash maps. Instead use EqualsInBuckets.
+    struct EqualsWithMargin {
+        bool operator()(const Fps& left, const Fps& right) const {
+            return left.equalsWithMargin(right);
+        }
+    };
+
+    // Equals comparator which can be used with hash maps.
+    // It's guaranteed that if two elements are equal, then their hashes are equal.
+    struct EqualsInBuckets {
+        bool operator()(const Fps& left, const Fps& right) const {
+            return left.getBucket() == right.getBucket();
+        }
+    };
+
+    inline friend std::string to_string(const Fps& fps) {
+        return base::StringPrintf("%.2ffps", fps.fps);
+    }
+
+    inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) {
+        return os << to_string(fps);
+    }
+
+private:
+    friend std::hash<android::Fps>;
+
+    constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {}
+
+    float getBucket() const { return std::round(fps / kMargin); }
+
+    static constexpr float kMargin = 0.001f;
+    float fps = 0;
+    nsecs_t period = 0;
+};
+
+static_assert(std::is_trivially_copyable_v<Fps>);
+
+} // namespace android
+
+namespace std {
+template <>
+struct hash<android::Fps> {
+    std::size_t operator()(const android::Fps& fps) const {
+        return std::hash<float>()(fps.getBucket());
+    }
+};
+} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index cfaa59d..b273712 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -74,6 +74,9 @@
 #define DEBUG_RESIZE 0
 
 namespace android {
+namespace {
+constexpr int kDumpTableRowLength = 159;
+} // namespace
 
 using base::StringAppendF;
 using namespace android::flag_operators;
@@ -627,7 +630,7 @@
 // ---------------------------------------------------------------------------
 
 std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareClientComposition(
-        compositionengine::LayerFE::ClientCompositionTargetSettings& /* targetSettings */) {
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     if (!getCompositionState()) {
         return {};
     }
@@ -649,8 +652,10 @@
 
     layerSettings.alpha = alpha;
     layerSettings.sourceDataspace = getDataSpace();
-    layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
-    layerSettings.blurRegions = getBlurRegions();
+    if (!targetSettings.disableBlurs) {
+        layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
+        layerSettings.blurRegions = getBlurRegions();
+    }
     return layerSettings;
 }
 
@@ -1274,7 +1279,8 @@
     t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
     if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
-        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER ignored");
+        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
+              "ROTATE_SURFACE_FLINGER ignored");
         return false;
     }
     mCurrentState.sequence++;
@@ -1423,7 +1429,8 @@
     // First traverse the tree and count how many layers has votes
     int layersWithVote = 0;
     traverseTree([&layersWithVote](Layer* layer) {
-        const auto layerVotedWithDefaultCompatibility = layer->mCurrentState.frameRate.rate > 0 &&
+        const auto layerVotedWithDefaultCompatibility =
+                layer->mCurrentState.frameRate.rate.isValid() &&
                 layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
         const auto layerVotedWithNoVote =
                 layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote;
@@ -1484,14 +1491,14 @@
 
 Layer::FrameRate Layer::getFrameRateForLayerTree() const {
     const auto frameRate = getDrawingState().frameRate;
-    if (frameRate.rate > 0 || frameRate.type == FrameRateCompatibility::NoVote) {
+    if (frameRate.rate.isValid() || frameRate.type == FrameRateCompatibility::NoVote) {
         return frameRate;
     }
 
     // This layer doesn't have a frame rate. If one of its ancestors or successors
     // have a vote, return a NoVote for ancestors/successors to set the vote
     if (getDrawingState().treeHasFrameRateVote) {
-        return {0, FrameRateCompatibility::NoVote};
+        return {Fps(0.0f), FrameRateCompatibility::NoVote};
     }
 
     return frameRate;
@@ -1619,11 +1626,8 @@
 }
 
 void Layer::miniDumpHeader(std::string& result) {
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------\n");
+    result.append(kDumpTableRowLength, '-');
+    result.append("\n");
     result.append(" Layer name\n");
     result.append("           Z | ");
     result.append(" Window Type | ");
@@ -1631,12 +1635,9 @@
     result.append(" Transform | ");
     result.append("  Disp Frame (LTRB) | ");
     result.append("         Source Crop (LTRB) | ");
-    result.append("    Frame Rate (Explicit) [Focused]\n");
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------------------");
-    result.append("-------------------\n");
+    result.append("    Frame Rate (Explicit) (Seamlessness) [Focused]\n");
+    result.append(kDumpTableRowLength, '-');
+    result.append("\n");
 }
 
 std::string Layer::frameRateCompatibilityString(Layer::FrameRateCompatibility compatibility) {
@@ -1685,23 +1686,20 @@
     const FloatRect& crop = outputLayerState.sourceCrop;
     StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
                   crop.bottom);
-    if (layerState.frameRate.rate != 0 ||
+    if (layerState.frameRate.rate.isValid() ||
         layerState.frameRate.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "% 6.2ffps %15s seamless=%s", layerState.frameRate.rate,
+        StringAppendF(&result, "%s %15s %17s", to_string(layerState.frameRate.rate).c_str(),
                       frameRateCompatibilityString(layerState.frameRate.type).c_str(),
                       toString(layerState.frameRate.seamlessness).c_str());
     } else {
-        StringAppendF(&result, "                         ");
+        result.append(41, ' ');
     }
 
     const auto focused = isLayerFocusedBasedOnPriority(getFrameRateSelectionPriority());
     StringAppendF(&result, "    [%s]\n", focused ? "*" : " ");
 
-    result.append("- - - - - - - - - - - - - - - - ");
-    result.append("- - - - - - - - - - - - - - - - ");
-    result.append("- - - - - - - - - - - - - - - - ");
-    result.append("- - - - - - - - - - - - - - - - ");
-    result.append("- - - - - - - -\n");
+    result.append(kDumpTableRowLength, '-');
+    result.append("\n");
 }
 
 void Layer::dumpFrameStats(std::string& result) const {
@@ -2437,8 +2435,8 @@
 InputWindowInfo Layer::fillInputInfo() {
     if (!hasInputInfo()) {
         mDrawingState.inputInfo.name = getName();
-        mDrawingState.inputInfo.ownerUid = mCallingUid;
-        mDrawingState.inputInfo.ownerPid = mCallingPid;
+        mDrawingState.inputInfo.ownerUid = mOwnerUid;
+        mDrawingState.inputInfo.ownerPid = mOwnerPid;
         mDrawingState.inputInfo.inputFeatures = InputWindowInfo::Feature::NO_INPUT_CHANNEL;
         mDrawingState.inputInfo.flags = InputWindowInfo::Flag::NOT_TOUCH_MODAL;
         mDrawingState.inputInfo.displayId = getLayerStack();
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index b30793a..cc47d12 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -45,6 +45,7 @@
 #include "ClientCache.h"
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
 #include "FrameTracker.h"
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
@@ -155,7 +156,7 @@
     struct FrameRate {
         using Seamlessness = scheduler::Seamlessness;
 
-        float rate;
+        Fps rate;
         FrameRateCompatibility type;
         Seamlessness seamlessness;
 
@@ -163,11 +164,12 @@
               : rate(0),
                 type(FrameRateCompatibility::Default),
                 seamlessness(Seamlessness::Default) {}
-        FrameRate(float rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
+        FrameRate(Fps rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
               : rate(rate), type(type), seamlessness(getSeamlessness(rate, shouldBeSeamless)) {}
 
         bool operator==(const FrameRate& other) const {
-            return rate == other.rate && type == other.type && seamlessness == other.seamlessness;
+            return rate.equalsWithMargin(other.rate) && type == other.type &&
+                    seamlessness == other.seamlessness;
         }
 
         bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -177,8 +179,8 @@
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
 
     private:
-        static Seamlessness getSeamlessness(float rate, bool shouldBeSeamless) {
-            if (rate == 0.0f) {
+        static Seamlessness getSeamlessness(Fps rate, bool shouldBeSeamless) {
+            if (!rate.isValid()) {
                 // Refresh rate of 0 is a special value which should reset the vote to
                 // its default value.
                 return Seamlessness::Default;
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index c5a4689..f273725 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -4,7 +4,5 @@
 chaviw@google.com
 lpy@google.com
 racarr@google.com
-steventhomas@google.com
 stoza@google.com
-vhau@google.com
 vishnun@google.com
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index f676d5b..f99d54a 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -190,7 +190,7 @@
 
     Mutex::Autolock _l(mFlinger.mStateLock);
     mLayer = mClient->getLayerUser(mIBinder);
-    mLayer->setFrameRate(Layer::FrameRate(0, Layer::FrameRateCompatibility::NoVote));
+    mLayer->setFrameRate(Layer::FrameRate(Fps(0.0f), Layer::FrameRateCompatibility::NoVote));
 
     // setting Layer's Z requires resorting layersSortedByZ
     ssize_t idx = mFlinger.mCurrentState.layersSortedByZ.indexOf(mLayer);
@@ -205,7 +205,7 @@
 void RefreshRateOverlay::primeCache() {
     auto& allRefreshRates = mFlinger.mRefreshRateConfigs->getAllRefreshRates();
     if (allRefreshRates.size() == 1) {
-        auto fps = allRefreshRates.begin()->second->getFps();
+        int fps = allRefreshRates.begin()->second->getFps().getIntValue();
         half4 color = {LOW_FPS_COLOR, ALPHA};
         mBufferCache.emplace(fps, SevenSegmentDrawer::drawNumber(fps, color, mShowSpinner));
         return;
@@ -214,7 +214,7 @@
     std::vector<uint32_t> supportedFps;
     supportedFps.reserve(allRefreshRates.size());
     for (auto& [ignored, refreshRate] : allRefreshRates) {
-        supportedFps.push_back(refreshRate->getFps());
+        supportedFps.push_back(refreshRate->getFps().getIntValue());
     }
 
     std::sort(supportedFps.begin(), supportedFps.end());
@@ -240,7 +240,7 @@
 }
 
 void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
-    mCurrentFps = refreshRate.getFps();
+    mCurrentFps = refreshRate.getFps().getIntValue();
     auto buffer = mBufferCache[*mCurrentFps][mFrame];
     mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {},
                       mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */));
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index c0d00f3..499daad 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -41,7 +41,7 @@
 
 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) {
+    if (layer.getFrameRateForLayerTree().rate.isValid()) {
         return true;
     }
 
@@ -86,9 +86,8 @@
 
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
-                                 LayerVoteType type) {
-    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+void LayerHistory::registerLayer(Layer* layer, Fps highRefreshRate, LayerVoteType type) {
+    const nsecs_t highRefreshRatePeriod = highRefreshRate.getPeriodNsecs();
     auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
     std::lock_guard lock(mLock);
     mLayerInfos.emplace_back(layer, std::move(info));
@@ -148,7 +147,7 @@
                 {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)));
+            trace(layer, *info, vote.type, vote.fps.getIntValue());
         }
     }
 
@@ -177,7 +176,7 @@
                 }
             }();
 
-            if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+            if (frameRate.rate.isValid() || voteType == LayerVoteType::NoVote) {
                 const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
                 info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
             } else {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 507ccc6..4214bab 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -46,7 +46,7 @@
     ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate, LayerVoteType type);
+    void registerLayer(Layer*, Fps highRefreshRate, LayerVoteType type);
 
     // Sets the display size. Client is responsible for synchronization.
     void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 66ac98a..1c0065c 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -38,7 +38,7 @@
       : mName(name),
         mHighRefreshRatePeriod(highRefreshRatePeriod),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, 0.0f}),
+        mLayerVote({defaultVote, Fps(0.0f)}),
         mRefreshRateHistory(name) {}
 
 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
@@ -91,7 +91,8 @@
 
     // 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;
+    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
+            .greaterThanOrEqualWithMargin(MIN_FPS_FOR_FREQUENT_LAYER);
 }
 
 bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -139,7 +140,7 @@
             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) {
+            if (!mLastRefreshRate.reported.isValid()) {
                 return std::nullopt;
             }
             continue;
@@ -163,7 +164,7 @@
     return static_cast<nsecs_t>(averageFrameTime);
 }
 
-std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -172,7 +173,7 @@
 
     const auto averageFrameTime = calculateAverageFrameTime();
     if (averageFrameTime.has_value()) {
-        const auto refreshRate = 1e9f / *averageFrameTime;
+        const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
         if (refreshRateConsistent) {
             const auto knownRefreshRate =
@@ -180,22 +181,23 @@
 
             // To avoid oscillation, use the last calculated refresh rate if it is
             // close enough
-            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
-                mLastRefreshRate.reported != knownRefreshRate) {
+            if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
+                        MARGIN &&
+                !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
                 mLastRefreshRate.calculated = refreshRate;
                 mLastRefreshRate.reported = knownRefreshRate;
             }
 
-            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
+            ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
+                  to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
         } else {
-            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
+            ALOGV("%s Not stable (%s) returning last known frame rate %s", mName.c_str(),
+                  to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
         }
     }
 
-    return mLastRefreshRate.reported == 0 ? std::nullopt
-                                          : std::make_optional(mLastRefreshRate.reported);
+    return mLastRefreshRate.reported.isValid() ? std::make_optional(mLastRefreshRate.reported)
+                                               : std::nullopt;
 }
 
 LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
@@ -207,13 +209,13 @@
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Max, 0};
+        return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
     }
 
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Min, 0};
+        return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
     }
 
     // If the layer was previously tagged as animating or infrequent, we clear
@@ -225,12 +227,12 @@
 
     auto refreshRate = calculateRefreshRateIfPossible(now);
     if (refreshRate.has_value()) {
-        ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+        ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
     }
 
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, 0};
+    return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
 }
 
 const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
@@ -256,7 +258,7 @@
     mRefreshRates.clear();
 }
 
-bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
     mRefreshRates.push_back({refreshRate, now});
     while (mRefreshRates.size() >= HISTORY_SIZE ||
            now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
@@ -268,7 +270,7 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
     return isConsistent();
@@ -279,15 +281,16 @@
 
     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;
+    const auto consistent =
+            max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_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->max.c_str(), max->refreshRate.getIntValue());
+        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
         ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index e434670..9304e62 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -50,9 +50,9 @@
     // 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 Fps 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;
+            std::chrono::nanoseconds(MIN_FPS_FOR_FREQUENT_LAYER.getPeriodNsecs()) + 1ms;
 
     friend class LayerHistoryTest;
 
@@ -60,7 +60,7 @@
     // Holds information about the layer vote
     struct LayerVote {
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        float fps = 0.0f;
+        Fps fps{0.0f};
         Seamlessness seamlessness = Seamlessness::Default;
     };
 
@@ -92,7 +92,7 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; }
 
     LayerVote getRefreshRateVote(nsecs_t now);
 
@@ -130,9 +130,9 @@
     // Holds information about the calculated and reported refresh rate
     struct RefreshRateHeuristicData {
         // Rate calculated on the layer
-        float calculated = 0.0f;
+        Fps calculated{0.0f};
         // Last reported rate for LayerInfo::getRefreshRate()
-        float reported = 0.0f;
+        Fps reported{0.0f};
         // Whether the last reported rate for LayerInfo::getRefreshRate()
         // was due to animation or infrequent updates
         bool animatingOrInfrequent = false;
@@ -151,18 +151,20 @@
         void clear();
 
         // Adds a new refresh rate and returns true if it is consistent
-        bool add(float refreshRate, nsecs_t now);
+        bool add(Fps refreshRate, nsecs_t now);
 
     private:
         friend class LayerHistoryTest;
 
         // Holds the refresh rate when it was calculated
         struct RefreshRateData {
-            float refreshRate = 0.0f;
+            Fps refreshRate{0.0f};
             nsecs_t timestamp = 0;
 
             bool operator<(const RefreshRateData& other) const {
-                return refreshRate < other.refreshRate;
+                // We don't need comparison with margins since we are using
+                // this to find the min and max refresh rates.
+                return refreshRate.getValue() < other.refreshRate.getValue();
             }
         };
 
@@ -180,13 +182,13 @@
         const std::string mName;
         mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
         std::deque<RefreshRateData> mRefreshRates;
-        static constexpr float MARGIN_FPS = 1.0;
+        static constexpr float MARGIN_CONSISTENT_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<Fps> calculateRefreshRateIfPossible(nsecs_t now);
     std::optional<nsecs_t> calculateAverageFrameTime() const;
     bool isFrameTimeValid(const FrameTimeData&) const;
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 83fa20e..4b7251b 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -29,10 +29,10 @@
 namespace android::scheduler {
 namespace {
 std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
-    return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %.2fHz",
-                              layer.name.c_str(),
+    return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %s", layer.name.c_str(),
                               RefreshRateConfigs::layerVoteTypeString(layer.vote).c_str(), weight,
-                              toString(layer.seamlessness).c_str(), layer.desiredRefreshRate);
+                              toString(layer.seamlessness).c_str(),
+                              to_string(layer.desiredRefreshRate).c_str());
 }
 } // namespace
 
@@ -41,7 +41,7 @@
 
 std::string RefreshRate::toString() const {
     return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
-                              getConfigId().value(), hwcConfig->getId(), getFps(),
+                              getConfigId().value(), hwcConfig->getId(), getFps().getValue(),
                               hwcConfig->getWidth(), hwcConfig->getHeight(), getConfigGroup());
 }
 
@@ -64,9 +64,9 @@
 
 std::string RefreshRateConfigs::Policy::toString() const {
     return base::StringPrintf("default config ID: %d, allowGroupSwitching = %d"
-                              ", primary range: [%.2f %.2f], app request range: [%.2f %.2f]",
-                              defaultConfig.value(), allowGroupSwitching, primaryRange.min,
-                              primaryRange.max, appRequestRange.min, appRequestRange.max);
+                              ", primary range: %s, app request range: %s",
+                              defaultConfig.value(), allowGroupSwitching,
+                              primaryRange.toString().c_str(), appRequestRange.toString().c_str());
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
@@ -144,7 +144,8 @@
     // move out the of range if layers explicitly request a different refresh
     // rate.
     const Policy* policy = getCurrentPolicyLocked();
-    const bool primaryRangeIsSingleRate = policy->primaryRange.min == policy->primaryRange.max;
+    const bool primaryRangeIsSingleRate =
+            policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
 
     if (!globalSignals.touch && globalSignals.idle &&
         !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
@@ -229,17 +230,18 @@
 
             // If the layer wants Max, give higher score to the higher refresh rate
             if (layer.vote == LayerVoteType::Max) {
-                const auto ratio = scores[i].first->fps / scores.back().first->fps;
+                const auto ratio =
+                        scores[i].first->fps.getValue() / scores.back().first->fps.getValue();
                 // use ratio^2 to get a lower score the more we get further from peak
                 const auto layerScore = ratio * ratio;
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore;
                 continue;
             }
 
             const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();
-            const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
+            const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
             if (layer.vote == LayerVoteType::ExplicitDefault) {
                 const auto layerScore = [&]() {
                     // Find the actual rate the layer will render, assuming
@@ -256,7 +258,7 @@
                 }();
 
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore;
                 continue;
             }
@@ -297,7 +299,7 @@
                 constexpr float kSeamedSwitchPenalty = 0.95f;
                 const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore * seamlessness;
                 continue;
             }
@@ -331,7 +333,7 @@
     const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
 
     if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&
-        bestRefreshRate->fps < touchRefreshRate.fps) {
+        bestRefreshRate->fps.lessThanWithMargin(touchRefreshRate.fps)) {
         setTouchConsidered();
         ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
         return touchRefreshRate;
@@ -347,9 +349,9 @@
     float max = begin->second;
     for (auto i = begin; i != end; ++i) {
         const auto [refreshRate, score] = *i;
-        ALOGV("%s scores %.2f", refreshRate->name.c_str(), score);
+        ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
 
-        ATRACE_INT(refreshRate->name.c_str(), round<int>(score * 100));
+        ATRACE_INT(refreshRate->getName().c_str(), round<int>(score * 100));
 
         if (score > max * (1 + EPSILON)) {
             max = score;
@@ -433,10 +435,10 @@
 
     for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
         const auto& config = configs.at(static_cast<size_t>(configId.value()));
-        const float fps = 1e9f / config->getVsyncPeriod();
         mRefreshRates.emplace(configId,
                               std::make_unique<RefreshRate>(configId, config,
-                                                            base::StringPrintf("%.2ffps", fps), fps,
+                                                            Fps::fromPeriodNsecs(
+                                                                    config->getVsyncPeriod()),
                                                             RefreshRate::ConstructorTag(0)));
         if (configId == currentConfigId) {
             mCurrentRefreshRate = mRefreshRates.at(configId).get();
@@ -463,8 +465,8 @@
         ALOGE("Default config is not in the primary range.");
         return false;
     }
-    return policy.appRequestRange.min <= policy.primaryRange.min &&
-            policy.appRequestRange.max >= policy.primaryRange.max;
+    return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) &&
+            policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max);
 }
 
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
@@ -550,12 +552,9 @@
     // Filter configs based on current policy and sort based on vsync period
     const Policy* policy = getCurrentPolicyLocked();
     const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig)->hwcConfig;
-    ALOGV("constructAvailableRefreshRates: default %d group %d primaryRange=[%.2f %.2f]"
-          " appRequestRange=[%.2f %.2f]",
-          policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->primaryRange.min,
-          policy->primaryRange.max, policy->appRequestRange.min, policy->appRequestRange.max);
+    ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
 
-    auto filterRefreshRates = [&](float min, float max, const char* listName,
+    auto filterRefreshRates = [&](Fps min, Fps max, const char* listName,
                                   std::vector<const RefreshRate*>* outRefreshRates) {
         getSortedRefreshRateList(
                 [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
@@ -572,12 +571,12 @@
                 outRefreshRates);
 
         LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
-                            "No matching configs for %s range: min=%.0f max=%.0f", listName, min,
-                            max);
+                            "No matching configs for %s range: min=%s max=%s", listName,
+                            to_string(min).c_str(), to_string(max).c_str());
         auto stringifyRefreshRates = [&]() -> std::string {
             std::string str;
             for (auto refreshRate : *outRefreshRates) {
-                base::StringAppendF(&str, "%s ", refreshRate->name.c_str());
+                base::StringAppendF(&str, "%s ", refreshRate->getName().c_str());
             }
             return str;
         };
@@ -590,39 +589,39 @@
                        &mAppRequestRefreshRates);
 }
 
-std::vector<float> RefreshRateConfigs::constructKnownFrameRates(
+std::vector<Fps> RefreshRateConfigs::constructKnownFrameRates(
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
-    std::vector<float> knownFrameRates = {24.0f, 30.0f, 45.0f, 60.0f, 72.0f};
+    std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
     knownFrameRates.reserve(knownFrameRates.size() + configs.size());
 
     // Add all supported refresh rates to the set
     for (const auto& config : configs) {
-        const auto refreshRate = 1e9f / config->getVsyncPeriod();
+        const auto refreshRate = Fps::fromPeriodNsecs(config->getVsyncPeriod());
         knownFrameRates.emplace_back(refreshRate);
     }
 
     // Sort and remove duplicates
-    const auto frameRatesEqual = [](float a, float b) { return std::abs(a - b) <= 0.01f; };
-    std::sort(knownFrameRates.begin(), knownFrameRates.end());
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
     knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      frameRatesEqual),
+                                      Fps::EqualsWithMargin()),
                           knownFrameRates.end());
     return knownFrameRates;
 }
 
-float RefreshRateConfigs::findClosestKnownFrameRate(float frameRate) const {
-    if (frameRate <= *mKnownFrameRates.begin()) {
+Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
+    if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
         return *mKnownFrameRates.begin();
     }
 
-    if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+    if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) {
         return *std::prev(mKnownFrameRates.end());
     }
 
-    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
+                                       Fps::comparesLess);
 
-    const auto distance1 = std::abs(frameRate - *lowerBound);
-    const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+    const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue()));
+    const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue()));
     return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
 }
 
@@ -657,7 +656,7 @@
 
     std::lock_guard lock(mLock);
     if (frameRateOverride.frameRateHz != 0) {
-        mPreferredRefreshRateForUid[frameRateOverride.uid] = frameRateOverride.frameRateHz;
+        mPreferredRefreshRateForUid[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
     } else {
         mPreferredRefreshRateForUid.erase(frameRateOverride.uid);
     }
@@ -675,7 +674,7 @@
     // in DisplayManagerService.getDisplayInfoForFrameRateOverride
     constexpr float kThreshold = 0.1f;
     const auto refreshRateHz = iter->second;
-    const auto numPeriods = mCurrentRefreshRate->getFps() / refreshRateHz;
+    const auto numPeriods = mCurrentRefreshRate->getFps().getValue() / refreshRateHz.getValue();
     const auto numPeriodsRounded = std::round(numPeriods);
     if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
         return 1;
@@ -690,7 +689,7 @@
     overrides.reserve(mPreferredRefreshRateForUid.size());
 
     for (const auto [uid, frameRate] : mPreferredRefreshRateForUid) {
-        overrides.emplace_back(FrameRateOverride{uid, frameRate});
+        overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
     }
 
     return overrides;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 6e0c0d3..ec7ffe5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -25,6 +25,7 @@
 #include <type_traits>
 
 #include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
 #include "HwcStrongTypes.h"
 #include "Scheduler/SchedulerUtils.h"
 #include "Scheduler/Seamlessness.h"
@@ -64,29 +65,31 @@
 
     public:
         RefreshRate(HwcConfigIndexType configId,
-                    std::shared_ptr<const HWC2::Display::Config> config, std::string name,
-                    float fps, ConstructorTag)
-              : configId(configId), hwcConfig(config), name(std::move(name)), fps(fps) {}
+                    std::shared_ptr<const HWC2::Display::Config> config, Fps fps, ConstructorTag)
+              : configId(configId), hwcConfig(config), fps(std::move(fps)) {}
 
         RefreshRate(const RefreshRate&) = delete;
 
         HwcConfigIndexType getConfigId() const { return configId; }
         nsecs_t getVsyncPeriod() const { return hwcConfig->getVsyncPeriod(); }
         int32_t getConfigGroup() const { return hwcConfig->getConfigGroup(); }
-        const std::string& getName() const { return name; }
-        float getFps() const { return fps; }
+        std::string getName() const { return to_string(fps); }
+        Fps getFps() const { return fps; }
 
         // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
-        // rate passed in. FPS_EPSILON is applied to the boundaries for approximation.
-        bool inPolicy(float minRefreshRate, float maxRefreshRate) const {
-            return (fps >= (minRefreshRate - FPS_EPSILON) && fps <= (maxRefreshRate + FPS_EPSILON));
+        // rate passed in. Margin of error is applied to the boundaries for approximation.
+        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
+            return minRefreshRate.lessThanOrEqualWithMargin(fps) &&
+                    fps.lessThanOrEqualWithMargin(maxRefreshRate);
         }
 
         bool operator!=(const RefreshRate& other) const {
             return configId != other.configId || hwcConfig != other.hwcConfig;
         }
 
-        bool operator<(const RefreshRate& other) const { return getFps() < other.getFps(); }
+        bool operator<(const RefreshRate& other) const {
+            return getFps().getValue() < other.getFps().getValue();
+        }
 
         bool operator==(const RefreshRate& other) const { return !(*this != other); }
 
@@ -96,18 +99,13 @@
         friend RefreshRateConfigs;
         friend class RefreshRateConfigsTest;
 
-        // The tolerance within which we consider FPS approximately equals.
-        static constexpr float FPS_EPSILON = 0.001f;
-
         // This config ID corresponds to the position of the config in the vector that is stored
         // on the device.
         const HwcConfigIndexType configId;
         // The config itself
         std::shared_ptr<const HWC2::Display::Config> hwcConfig;
-        // Human readable name of the refresh rate.
-        const std::string name;
         // Refresh rate in frames per second
-        const float fps = 0;
+        const Fps fps{0.0f};
     };
 
     using AllRefreshRatesMapType =
@@ -119,14 +117,19 @@
 
     public:
         struct Range {
-            float min = 0;
-            float max = std::numeric_limits<float>::max();
+            Fps min{0.0f};
+            Fps max{std::numeric_limits<float>::max()};
 
             bool operator==(const Range& other) const {
-                return min == other.min && max == other.max;
+                return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
             }
 
             bool operator!=(const Range& other) const { return !(*this == other); }
+
+            std::string toString() const {
+                return base::StringPrintf("[%s %s]", to_string(min).c_str(),
+                                          to_string(max).c_str());
+            }
         };
 
         // The default config, used to ensure we only initiate display config switches within the
@@ -221,7 +224,7 @@
         // Layer vote type.
         LayerVoteType vote = LayerVoteType::NoVote;
         // Layer's desired refresh rate, if applicable.
-        float desiredRefreshRate = 0.0f;
+        Fps desiredRefreshRate{0.0f};
         // If a seamless mode switch is required.
         Seamlessness seamlessness = Seamlessness::Default;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
@@ -232,7 +235,7 @@
 
         bool operator==(const LayerRequirement& other) const {
             return name == other.name && vote == other.vote &&
-                    desiredRefreshRate == other.desiredRefreshRate &&
+                    desiredRefreshRate.equalsWithMargin(other.desiredRefreshRate) &&
                     seamlessness == other.seamlessness && weight == other.weight &&
                     focused == other.focused;
         }
@@ -295,7 +298,7 @@
     static std::string layerVoteTypeString(LayerVoteType vote);
 
     // Returns a known frame rate that is the closest to frameRate
-    float findClosestKnownFrameRate(float frameRate) const;
+    Fps findClosestKnownFrameRate(Fps frameRate) const;
 
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
@@ -332,7 +335,7 @@
     friend class RefreshRateConfigsTest;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
-    static std::vector<float> constructKnownFrameRates(
+    static std::vector<Fps> constructKnownFrameRates(
             const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
 
     void getSortedRefreshRateList(
@@ -387,7 +390,7 @@
 
     // A mapping between a UID and a preferred refresh rate that this app would
     // run at.
-    std::unordered_map<uid_t, float> mPreferredRefreshRateForUid GUARDED_BY(mLock);
+    std::unordered_map<uid_t, Fps> mPreferredRefreshRateForUid GUARDED_BY(mLock);
 
     // The min and max refresh rates supported by the device.
     // This will not change at runtime.
@@ -398,7 +401,7 @@
 
     // A sorted list of known frame rates that a Heuristic layer will choose
     // from based on the closest value.
-    const std::vector<float> mKnownFrameRates;
+    const std::vector<Fps> mKnownFrameRates;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index d9e7b37..80f4665 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -18,7 +18,7 @@
 
 #include <numeric>
 
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Fps.h"
 #include "Scheduler/SchedulerUtils.h"
 #include "TimeStats/TimeStats.h"
 
@@ -40,12 +40,10 @@
     static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR;
 
 public:
-    RefreshRateStats(const RefreshRateConfigs& refreshRateConfigs, TimeStats& timeStats,
-                     HwcConfigIndexType currentConfigId,
+    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate,
                      android::hardware::graphics::composer::hal::PowerMode currentPowerMode)
-          : mRefreshRateConfigs(refreshRateConfigs),
-            mTimeStats(timeStats),
-            mCurrentConfigMode(currentConfigId),
+          : mTimeStats(timeStats),
+            mCurrentRefreshRate(currentRefreshRate),
             mCurrentPowerMode(currentPowerMode) {}
 
     // Sets power mode.
@@ -59,12 +57,12 @@
 
     // Sets config mode. If the mode has changed, it records how much time was spent in the previous
     // mode.
-    void setConfigMode(HwcConfigIndexType configId) {
-        if (mCurrentConfigMode == configId) {
+    void setRefreshRate(Fps currRefreshRate) {
+        if (mCurrentRefreshRate.equalsWithMargin(currRefreshRate)) {
             return;
         }
         flushTime();
-        mCurrentConfigMode = configId;
+        mCurrentRefreshRate = currRefreshRate;
     }
 
     // Returns a map between human readable refresh rate and number of seconds the device spent in
@@ -79,10 +77,10 @@
         // Multiple configs may map to the same name, e.g. "60fps". Add the
         // times for such configs together.
         for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] = 0;
+            totalTime[to_string(configId)] = 0;
         }
         for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] += time;
+            totalTime[to_string(configId)] += time;
         }
         totalTime["ScreenOff"] = mScreenOffTime;
         return totalTime;
@@ -111,12 +109,11 @@
         uint32_t fps = 0;
         if (mCurrentPowerMode == android::hardware::graphics::composer::hal::PowerMode::ON) {
             // Normal power mode is counted under different config modes.
-            if (mConfigModesTotalTime.find(mCurrentConfigMode) == mConfigModesTotalTime.end()) {
-                mConfigModesTotalTime[mCurrentConfigMode] = 0;
+            if (mConfigModesTotalTime.find(mCurrentRefreshRate) == mConfigModesTotalTime.end()) {
+                mConfigModesTotalTime[mCurrentRefreshRate] = 0;
             }
-            mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
-            fps = static_cast<uint32_t>(std::round(
-                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).getFps()));
+            mConfigModesTotalTime[mCurrentRefreshRate] += timeElapsedMs;
+            fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
         } else {
             mScreenOffTime += timeElapsedMs;
         }
@@ -134,16 +131,13 @@
                                   days, hours, mins, sec, secRemainderMs);
     }
 
-    // Keeps information about refresh rate configs that device has.
-    const RefreshRateConfigs& mRefreshRateConfigs;
-
     // Aggregate refresh rate statistics for telemetry.
     TimeStats& mTimeStats;
 
-    HwcConfigIndexType mCurrentConfigMode;
+    Fps mCurrentRefreshRate;
     android::hardware::graphics::composer::hal::PowerMode mCurrentPowerMode;
 
-    std::unordered_map<HwcConfigIndexType /* configId */, int64_t /* duration in ms */>
+    std::unordered_map<Fps, int64_t /* duration in ms */, std::hash<Fps>, Fps::EqualsInBuckets>
             mConfigModesTotalTime;
     int64_t mScreenOffTime = 0;
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 52bf483..07411b0 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -510,25 +510,22 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto minFps = mRefreshRateConfigs.getMinRefreshRate().getFps();
     const auto maxFps = mRefreshRateConfigs.getMaxRefreshRate().getFps();
 
     if (layer->getWindowType() == InputWindowInfo::Type::STATUS_BAR) {
-        mLayerHistory->registerLayer(layer, minFps, maxFps,
-                                     scheduler::LayerHistory::LayerVoteType::NoVote);
+        mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::NoVote);
     } else if (!mOptions.useContentDetection) {
         // If the content detection feature is off, all layers are registered at Max. We still keep
         // the layer history, since we use it for other features (like Frame Rate API), so layers
         // still need to be registered.
-        mLayerHistory->registerLayer(layer, minFps, maxFps,
-                                     scheduler::LayerHistory::LayerVoteType::Max);
+        mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::Max);
     } else {
         if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
             // Running Wallpaper at Min is considered as part of content detection.
-            mLayerHistory->registerLayer(layer, minFps, maxFps,
+            mLayerHistory->registerLayer(layer, maxFps,
                                          scheduler::LayerHistory::LayerVoteType::Min);
         } else {
-            mLayerHistory->registerLayer(layer, minFps, maxFps,
+            mLayerHistory->registerLayer(layer, maxFps,
                                          scheduler::LayerHistory::LayerVoteType::Heuristic);
         }
     }
@@ -618,14 +615,15 @@
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
     const auto& refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
-    constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
-    if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER{65.0f};
+    if (state == TimerState::Reset &&
+        refreshRate.getFps().greaterThanWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
         resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
     } else if (state == TimerState::Expired &&
-               refreshRate.getFps() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+               refreshRate.getFps().lessThanOrEqualWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the VsyncController model anyway.
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index aac2569..8431323 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -31,15 +31,10 @@
     return std::nullopt;
 }
 
-bool fpsEqualsWithMargin(float fpsA, float fpsB) {
-    static constexpr float MARGIN = 0.01f;
-    return std::abs(fpsA - fpsB) <= MARGIN;
-}
-
-std::vector<float> getRefreshRatesFromConfigs(
+std::vector<android::Fps> getRefreshRatesFromConfigs(
         const android::scheduler::RefreshRateConfigs& refreshRateConfigs) {
     const auto& allRefreshRates = refreshRateConfigs.getAllRefreshRates();
-    std::vector<float> refreshRates;
+    std::vector<android::Fps> refreshRates;
     refreshRates.reserve(allRefreshRates.size());
 
     for (const auto& [ignored, refreshRate] : allRefreshRates) {
@@ -53,12 +48,12 @@
 
 namespace android::scheduler::impl {
 
-VsyncConfiguration::VsyncConfiguration(float currentFps) : mRefreshRateFps(currentFps) {}
+VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {}
 
-PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(float fps) const {
+PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const {
     const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(),
-                                   [&fps](const std::pair<float, VsyncConfigSet>& candidateFps) {
-                                       return fpsEqualsWithMargin(fps, candidateFps.first);
+                                   [&fps](const std::pair<Fps, VsyncConfigSet>& candidateFps) {
+                                       return fps.equalsWithMargin(candidateFps.first);
                                    });
 
     if (iter != mOffsets.end()) {
@@ -67,13 +62,13 @@
 
     // Unknown refresh rate. This might happen if we get a hotplug event for an external display.
     // In this case just construct the offset.
-    ALOGW("Can't find offset for %.2f fps", fps);
-    return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
+    ALOGW("Can't find offset for %s", to_string(fps).c_str());
+    return constructOffsets(fps.getPeriodNsecs());
 }
 
-void VsyncConfiguration::initializeOffsets(const std::vector<float>& refreshRates) {
+void VsyncConfiguration::initializeOffsets(const std::vector<Fps>& refreshRates) {
     for (const auto fps : refreshRates) {
-        mOffsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
+        mOffsets.emplace(fps, constructOffsets(fps.getPeriodNsecs()));
     }
 }
 
@@ -127,7 +122,7 @@
                              .value_or(std::numeric_limits<nsecs_t>::max())) {}
 
 PhaseOffsets::PhaseOffsets(
-        const std::vector<float>& refreshRates, float currentFps, nsecs_t vsyncPhaseOffsetNs,
+        const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
         nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
         std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
         std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
@@ -378,10 +373,9 @@
     validateSysprops();
 }
 
-WorkDuration::WorkDuration(const std::vector<float>& refreshRates, float currentFps,
-                           nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
-                           nsecs_t appEarlyDuration, nsecs_t sfEarlyGpuDuration,
-                           nsecs_t appEarlyGpuDuration)
+WorkDuration::WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
+                           nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+                           nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration)
       : VsyncConfiguration(currentFps),
         mSfDuration(sfDuration),
         mAppDuration(appDuration),
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index c27a25d..a120e97 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -18,6 +18,9 @@
 
 #include <unordered_map>
 
+#include <utils/Timers.h>
+
+#include "Fps.h"
 #include "RefreshRateConfigs.h"
 #include "VsyncModulator.h"
 
@@ -35,9 +38,9 @@
 
     virtual ~VsyncConfiguration() = default;
     virtual VsyncConfigSet getCurrentConfigs() const = 0;
-    virtual VsyncConfigSet getConfigsForRefreshRate(float fps) const = 0;
+    virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0;
 
-    virtual void setRefreshRateFps(float fps) = 0;
+    virtual void setRefreshRateFps(Fps fps) = 0;
 
     virtual void dump(std::string& result) const = 0;
 };
@@ -51,10 +54,10 @@
  */
 class VsyncConfiguration : public scheduler::VsyncConfiguration {
 public:
-    explicit VsyncConfiguration(float currentFps);
+    explicit VsyncConfiguration(Fps currentFps);
 
     // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    VsyncConfigSet getConfigsForRefreshRate(float fps) const override;
+    VsyncConfigSet getConfigsForRefreshRate(Fps fps) const override;
 
     // Returns early, early GL, and late offsets for Apps and SF.
     VsyncConfigSet getCurrentConfigs() const override {
@@ -63,17 +66,17 @@
 
     // This function should be called when the device is switching between different
     // refresh rates, to properly update the offsets.
-    void setRefreshRateFps(float fps) override { mRefreshRateFps = fps; }
+    void setRefreshRateFps(Fps fps) override { mRefreshRateFps = fps; }
 
     // Returns current offsets in human friendly format.
     void dump(std::string& result) const override;
 
 protected:
-    void initializeOffsets(const std::vector<float>& refreshRates);
+    void initializeOffsets(const std::vector<Fps>& refreshRates);
     virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0;
 
-    std::unordered_map<float, VsyncConfigSet> mOffsets;
-    std::atomic<float> mRefreshRateFps;
+    std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets> mOffsets;
+    std::atomic<Fps> mRefreshRateFps;
 };
 
 /*
@@ -86,10 +89,9 @@
 
 protected:
     // Used for unit tests
-    PhaseOffsets(const std::vector<float>& refreshRates, float currentFps,
-                 nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
-                 std::optional<nsecs_t> earlySfOffsetNs, std::optional<nsecs_t> earlyGpuSfOffsetNs,
-                 std::optional<nsecs_t> earlyAppOffsetNs,
+    PhaseOffsets(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
+                 nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
+                 std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
                  std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
                  nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
                  std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
@@ -130,7 +132,7 @@
 
 protected:
     // Used for unit tests
-    WorkDuration(const std::vector<float>& refreshRates, float currentFps, nsecs_t sfDuration,
+    WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
                  nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
                  nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index cb56ecc..e9416d6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -274,6 +274,7 @@
 
 const String16 sHardwareTest("android.permission.HARDWARE_TEST");
 const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER");
+const String16 sRotateSurfaceFlinger("android.permission.ROTATE_SURFACE_FLINGER");
 const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
 const String16 sDump("android.permission.DUMP");
 const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
@@ -324,6 +325,14 @@
     }
 }
 
+bool callingThreadHasRotateSurfaceFlingerAccess() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
+            PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid);
+}
+
 SurfaceFlingerBE::SurfaceFlingerBE() : mHwcServiceName(getHwcServiceName()) {}
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
@@ -921,10 +930,10 @@
         }
 
         const nsecs_t period = hwConfig->getVsyncPeriod();
-        config.refreshRate = 1e9f / period;
+        config.refreshRate = Fps::fromPeriodNsecs(period).getValue();
 
         const auto vsyncConfigSet =
-                mVsyncConfiguration->getConfigsForRefreshRate(config.refreshRate);
+                mVsyncConfiguration->getConfigsForRefreshRate(Fps(config.refreshRate));
         config.appVsyncOffset = vsyncConfigSet.late.appOffset;
         config.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
         config.configGroup = hwConfig->getConfigGroup();
@@ -1042,7 +1051,7 @@
             return INVALID_OPERATION;
         } else {
             const HwcConfigIndexType config(mode);
-            const float fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
+            const auto fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
             // Keep the old switching type.
             const auto allowGroupSwitching =
                     mRefreshRateConfigs->getCurrentPolicy().allowGroupSwitching;
@@ -1071,16 +1080,17 @@
 
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
     mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
-    mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
     display->setActiveConfig(mUpcomingActiveConfig.configId);
 
     auto& refreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
+    mRefreshRateStats->setRefreshRate(refreshRate.getFps());
+
     if (refreshRate.getVsyncPeriod() != oldRefreshRate.getVsyncPeriod()) {
         mTimeStats->incrementRefreshRateSwitches();
     }
     updatePhaseConfiguration(refreshRate);
-    ATRACE_INT("ActiveConfigFPS", refreshRate.getFps());
+    ATRACE_INT("ActiveConfigFPS", refreshRate.getFps().getValue());
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
         const nsecs_t vsyncPeriod =
@@ -1138,7 +1148,7 @@
     mUpcomingActiveConfig = *desiredActiveConfig;
     const auto displayId = display->getPhysicalId();
 
-    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps());
+    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps().getValue());
 
     // TODO(b/142753666) use constrains
     hal::VsyncPeriodChangeConstraints constraints;
@@ -1617,7 +1627,7 @@
 
 void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hal::HWDisplayId hwcDisplayId,
                                        hal::Connection connection) {
-    ALOGV("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId,
+    ALOGI("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId,
           connection == hal::Connection::CONNECTED ? "connected" : "disconnected");
 
     // Ignore events that do not have the right sequenceId.
@@ -2113,7 +2123,7 @@
     ALOGV("postComposition");
 
     nsecs_t dequeueReadyTime = systemTime();
-    for (auto layer : mLayersWithQueuedFrames) {
+    for (const auto& layer : mLayersWithQueuedFrames) {
         layer->releasePendingBuffer(dequeueReadyTime);
     }
 
@@ -2386,6 +2396,8 @@
 }
 
 void SurfaceFlinger::dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected) {
+    ALOGI("Dispatching display hotplug event displayId=%s, connected=%d",
+          to_string(displayId).c_str(), connected);
     mScheduler->onHotplugReceived(mAppConnectionHandle, displayId, connected);
     mScheduler->onHotplugReceived(mSfConnectionHandle, displayId, connected);
 }
@@ -2561,7 +2573,8 @@
 }
 
 void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
-    if (const auto display = getDisplayDeviceLocked(displayToken)) {
+    auto display = getDisplayDeviceLocked(displayToken);
+    if (display) {
         display->disconnect();
         if (!display->isVirtual()) {
             dispatchDisplayHotplugEvent(display->getPhysicalId(), false);
@@ -2569,6 +2582,22 @@
     }
 
     mDisplays.erase(displayToken);
+
+    if (display && display->isVirtual()) {
+        static_cast<void>(schedule([display = std::move(display)] {
+            // Destroy the display without holding the mStateLock.
+            // This is a temporary solution until we can manage transaction queues without
+            // holding the mStateLock.
+            // With blast, the IGBP that is passed to the VirtualDisplaySurface is owned by the
+            // client. When the IGBP is disconnected, its buffer cache in SF will be cleared
+            // via SurfaceComposerClient::doUncacheBufferTransaction. This call from the client
+            // ends up running on the main thread causing a deadlock since setTransactionstate
+            // will try to acquire the mStateLock. Instead we extend the lifetime of
+            // DisplayDevice and destroy it in the main thread without holding the mStateLock.
+            // The display will be disconnected and removed from the mDisplays list so it will
+            // not be accessible.
+        }));
+    }
 }
 
 void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken,
@@ -2870,10 +2899,10 @@
             std::make_unique<scheduler::RefreshRateConfigs>(getHwComposer().getConfigs(
                                                                     primaryDisplayId),
                                                             currentConfig);
+    const auto& currRefreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig);
     mRefreshRateStats =
-            std::make_unique<scheduler::RefreshRateStats>(*mRefreshRateConfigs, *mTimeStats,
-                                                          currentConfig, hal::PowerMode::OFF);
-    mRefreshRateStats->setConfigMode(currentConfig);
+            std::make_unique<scheduler::RefreshRateStats>(*mTimeStats, currRefreshRate.getFps(),
+                                                          hal::PowerMode::OFF);
 
     mVsyncConfiguration = getFactory().createVsyncConfiguration(*mRefreshRateConfigs);
     mVsyncModulator.emplace(mVsyncConfiguration->getCurrentConfigs());
@@ -2881,8 +2910,7 @@
     // start the EventThread
     mScheduler = getFactory().createScheduler(*mRefreshRateConfigs, *this);
     const auto configs = mVsyncConfiguration->getCurrentConfigs();
-    const nsecs_t vsyncPeriod =
-            mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig).getVsyncPeriod();
+    const nsecs_t vsyncPeriod = currRefreshRate.getVsyncPeriod();
     mAppConnectionHandle =
             mScheduler->createConnection("app", mFrameTimeline->getTokenManager(),
                                          /*workDuration=*/configs.late.appWorkDuration,
@@ -3058,7 +3086,7 @@
         // writes to Layer current state. See also b/119481871
         Mutex::Autolock lock(mStateLock);
 
-        for (auto& layer : mLayersWithQueuedFrames) {
+        for (const auto& layer : mLayersWithQueuedFrames) {
             if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
                 mLayersPendingRefresh.push_back(layer);
             }
@@ -3654,15 +3682,20 @@
         // must now be cropped to a non rectangular 8 sided region.
         //
         // Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
-        // private API, and the WindowManager only uses rotation in one case, which is on a top
-        // level layer in which cropping is not an issue.
+        // private API, and arbitrary rotation is used in limited use cases, for instance:
+        // - WindowManager only uses rotation in one case, which is on a top level layer in which
+        //   cropping is not an issue.
+        // - Launcher, as a privileged app, uses this to transition an application to PiP
+        //   (picture-in-picture) mode.
         //
         // However given that abuse of rotation matrices could lead to surfaces extending outside
-        // of cropped areas, we need to prevent non-root clients without permission ACCESS_SURFACE_FLINGER
-        // (a.k.a. everyone except WindowManager and tests) from setting non rectangle preserving
-        // transformations.
-        if (layer->setMatrix(s.matrix, privileged))
-            flags |= eTraversalNeeded;
+        // of cropped areas, we need to prevent non-root clients without permission
+        // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
+        // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
+        // preserving transformations.
+        bool allowNonRectPreservingTransforms =
+                privileged || callingThreadHasRotateSurfaceFlingerAccess();
+        if (layer->setMatrix(s.matrix, allowNonRectPreservingTransforms)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTransparentRegionChanged) {
         if (layer->setTransparentRegionHint(s.transparentRegion))
@@ -3777,7 +3810,7 @@
     if (what & layer_state_t::eFrameRateChanged) {
         if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility,
                               "SurfaceFlinger::setClientStateLocked") &&
-            layer->setFrameRate(Layer::FrameRate(s.frameRate,
+            layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate),
                                                  Layer::FrameRate::convertCompatibility(
                                                          s.frameRateCompatibility),
                                                  s.shouldBeSeamless))) {
@@ -4710,8 +4743,8 @@
         const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         std::string fps, xDpi, yDpi;
         if (activeConfig) {
-            fps = base::StringPrintf("%.2f Hz",
-                                     1e9f / getHwComposer().getDisplayVsyncPeriod(*displayId));
+            const auto vsyncPeriod = getHwComposer().getDisplayVsyncPeriod(*displayId);
+            fps = base::StringPrintf("%s", to_string(Fps::fromPeriodNsecs(vsyncPeriod)).c_str());
             xDpi = base::StringPrintf("%.2f", activeConfig->getDpiX());
             yDpi = base::StringPrintf("%.2f", activeConfig->getDpiY());
         } else {
@@ -5798,7 +5831,10 @@
     const auto display = renderArea.getDisplayDevice();
     std::vector<Layer*> renderedLayers;
     Region clearRegion = Region::INVALID_REGION;
+    bool disableBlurs = false;
     traverseLayers([&](Layer* layer) {
+        disableBlurs |= layer->getCurrentState().sidebandStream != nullptr;
+
         Region clip(renderArea.getBounds());
         compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
                 clip,
@@ -5809,8 +5845,9 @@
                 clearRegion,
                 layerStackSpaceRect,
                 clientCompositionDisplay.outputDataspace,
-                true,  /* realContentIsVisible */
+                true, /* realContentIsVisible */
                 false, /* clearContent */
+                disableBlurs,
         };
         std::vector<compositionengine::LayerFE::LayerSettings> results =
                 layer->prepareClientCompositionList(targetSettings);
@@ -5825,11 +5862,13 @@
                     settings.backgroundBlurRadius = 0;
                 }
             }
+
             clientCompositionLayers.insert(clientCompositionLayers.end(),
                                            std::make_move_iterator(results.begin()),
                                            std::make_move_iterator(results.end()));
             renderedLayers.push_back(layer);
         }
+
     });
 
     std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers(
@@ -5961,11 +6000,7 @@
     }
     scheduler::RefreshRateConfigs::Policy currentPolicy = mRefreshRateConfigs->getCurrentPolicy();
 
-    ALOGV("Setting desired display config specs: defaultConfig: %d primaryRange: [%.0f %.0f]"
-          " expandedRange: [%.0f %.0f]",
-          currentPolicy.defaultConfig.value(), currentPolicy.primaryRange.min,
-          currentPolicy.primaryRange.max, currentPolicy.appRequestRange.min,
-          currentPolicy.appRequestRange.max);
+    ALOGV("Setting desired display config specs: %s", currentPolicy.toString().c_str());
 
     // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
     // be depending in this callback.
@@ -6021,8 +6056,8 @@
             using Policy = scheduler::RefreshRateConfigs::Policy;
             const Policy policy{HwcConfigIndexType(defaultConfig),
                                 allowGroupSwitching,
-                                {primaryRefreshRateMin, primaryRefreshRateMax},
-                                {appRequestRefreshRateMin, appRequestRefreshRateMax}};
+                                {Fps(primaryRefreshRateMin), Fps(primaryRefreshRateMax)},
+                                {Fps(appRequestRefreshRateMin), Fps(appRequestRefreshRateMax)}};
             constexpr bool kOverridePolicy = false;
 
             return setDesiredDisplayConfigSpecsInternal(display, policy, kOverridePolicy);
@@ -6054,10 +6089,10 @@
                 mRefreshRateConfigs->getDisplayManagerPolicy();
         *outDefaultConfig = policy.defaultConfig.value();
         *outAllowGroupSwitching = policy.allowGroupSwitching;
-        *outPrimaryRefreshRateMin = policy.primaryRange.min;
-        *outPrimaryRefreshRateMax = policy.primaryRange.max;
-        *outAppRequestRefreshRateMin = policy.appRequestRange.min;
-        *outAppRequestRefreshRateMax = policy.appRequestRange.max;
+        *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue();
+        *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue();
+        *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue();
+        *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue();
         return NO_ERROR;
     } else if (display->isVirtual()) {
         return INVALID_OPERATION;
@@ -6066,10 +6101,10 @@
         *outDefaultConfig = getHwComposer().getActiveConfigIndex(displayId);
         *outAllowGroupSwitching = false;
         auto vsyncPeriod = getHwComposer().getActiveConfig(displayId)->getVsyncPeriod();
-        *outPrimaryRefreshRateMin = 1e9f / vsyncPeriod;
-        *outPrimaryRefreshRateMax = 1e9f / vsyncPeriod;
-        *outAppRequestRefreshRateMin = 1e9f / vsyncPeriod;
-        *outAppRequestRefreshRateMax = 1e9f / vsyncPeriod;
+        *outPrimaryRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outPrimaryRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outAppRequestRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outAppRequestRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
         return NO_ERROR;
     }
 }
@@ -6165,7 +6200,7 @@
                 return BAD_VALUE;
             }
             if (layer->setFrameRate(
-                        Layer::FrameRate(frameRate,
+                        Layer::FrameRate(Fps{frameRate},
                                          Layer::FrameRate::convertCompatibility(compatibility),
                                          shouldBeSeamless))) {
                 setTransactionFlags(eTraversalNeeded);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a909ad8..0509247 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1091,9 +1091,8 @@
     bool mAnimCompositionPending = false;
 
     // Tracks layers that have pending frames which are candidates for being
-    // latched. Because this contains a set of raw layer pointers, can only be
-    // mutated on the main thread.
-    std::unordered_set<Layer*> mLayersWithQueuedFrames;
+    // latched.
+    std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> mLayersWithQueuedFrames;
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
     std::array<sp<Fence>, 2> mPreviousPresentFences = {Fence::NO_FENCE, Fence::NO_FENCE};
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index 9fd2227..18e0806 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -52,6 +52,8 @@
     virtualDisplay.clear();
     // Sync here to ensure the display was completely destroyed in SF
     t.apply(true);
+    // add another sync since we are deferring the display destruction
+    t.apply(true);
 
     sp<Surface> surface = new Surface(mProducer);
     sp<ANativeWindow> window(surface);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 871222c..a00e959 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -44,6 +44,7 @@
         "DisplayDevice_GetBestColorModeTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
         "EventThreadTest.cpp",
+        "FpsTest.cpp",
         "FrameTimelineTest.cpp",
         "HWComposerTest.cpp",
         "OneShotTimerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
index 4cd1e0a..36e24d2 100644
--- a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
+++ b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
@@ -26,7 +26,7 @@
     static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
     static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
 
-    VsyncConfigSet getConfigsForRefreshRate(float) const override { return getCurrentConfigs(); }
+    VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
 
     VsyncConfigSet getCurrentConfigs() const override {
         return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
@@ -37,7 +37,7 @@
                  FAKE_DURATION_OFFSET_NS}};
     }
 
-    void setRefreshRateFps(float) override {}
+    void setRefreshRateFps(Fps) override {}
     void dump(std::string&) const override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/FpsTest.cpp b/services/surfaceflinger/tests/unittests/FpsTest.cpp
new file mode 100644
index 0000000..db732cf
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FpsTest.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Fps.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+TEST(FpsTest, construct) {
+    Fps fpsDefault;
+    EXPECT_FALSE(fpsDefault.isValid());
+
+    Fps fps1(60.0f);
+    EXPECT_TRUE(fps1.isValid());
+    Fps fps2 = Fps::fromPeriodNsecs(static_cast<nsecs_t>(1e9f / 60.0f));
+    EXPECT_TRUE(fps2.isValid());
+    EXPECT_TRUE(fps1.equalsWithMargin(fps2));
+}
+
+TEST(FpsTest, compare) {
+    constexpr float kEpsilon = 1e-4f;
+    const Fps::EqualsInBuckets equalsInBuckets;
+    const Fps::EqualsWithMargin equalsWithMargin;
+
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f - kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f + kEpsilon)));
+
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f)));
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f - kEpsilon)));
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f)));
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f - kEpsilon)));
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+    // Fps with difference of 1 should be different
+    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(61.f)));
+    EXPECT_TRUE(Fps(60.0f).lessThanWithMargin(Fps(61.f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.f)));
+
+    // These are common refresh rates which should be different.
+    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(59.94f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.94f)));
+    EXPECT_FALSE(equalsInBuckets(Fps(60.0f), Fps(59.94f)));
+    EXPECT_FALSE(equalsWithMargin(Fps(60.0f), Fps(59.94f)));
+    EXPECT_NE(std::hash<Fps>()(Fps(60.0f)), std::hash<Fps>()(Fps(59.94f)));
+
+    EXPECT_FALSE(Fps(30.0f).equalsWithMargin(Fps(29.97f)));
+    EXPECT_TRUE(Fps(30.0f).greaterThanWithMargin(Fps(29.97f)));
+    EXPECT_FALSE(equalsInBuckets(Fps(30.0f), Fps(29.97f)));
+    EXPECT_FALSE(equalsWithMargin(Fps(30.0f), Fps(29.97f)));
+    EXPECT_NE(std::hash<Fps>()(Fps(30.0f)), std::hash<Fps>()(Fps(29.97f)));
+}
+
+TEST(FpsTest, getIntValue) {
+    EXPECT_EQ(30, Fps(30.1f).getIntValue());
+    EXPECT_EQ(31, Fps(30.9f).getIntValue());
+    EXPECT_EQ(31, Fps(30.5f).getIntValue());
+}
+
+TEST(FpsTest, equalsInBucketsImpliesEqualHashes) {
+    constexpr float kStep = 1e-4f;
+    const Fps::EqualsInBuckets equals;
+    for (float fps = 30.0f; fps < 31.0f; fps += kStep) {
+        const Fps left(fps);
+        const Fps right(fps + kStep);
+        if (equals(left, right)) {
+            ASSERT_EQ(std::hash<Fps>()(left), std::hash<Fps>()(right))
+                    << "left= " << left << " right=" << right;
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index fbb4637..cbc1e02 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -32,7 +32,9 @@
 using testing::_;
 using testing::Return;
 
-namespace android::scheduler {
+namespace android {
+
+namespace scheduler {
 
 class LayerHistoryTest : public testing::Test {
 protected:
@@ -43,11 +45,11 @@
     static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
             LayerInfo::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 Fps LO_FPS{30.f};
+    static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
 
-    static constexpr float HI_FPS = 90.f;
-    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
+    static constexpr Fps HI_FPS{90.f};
+    static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
 
     LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
@@ -88,20 +90,19 @@
         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);
+    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, Fps frameRate,
+                               Fps desiredRefreshRate, int numFrames) {
         LayerHistory::Summary summary;
         for (int i = 0; i < numFrames; i++) {
             history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-            time += framePeriod;
+            time += frameRate.getPeriodNsecs();
 
             summary = history().summarize(time);
         }
 
         ASSERT_EQ(1, summary.size());
         ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+        ASSERT_TRUE(desiredRefreshRate.equalsWithMargin(summary[0].desiredRefreshRate))
                 << "Frame rate is " << frameRate;
     }
 
@@ -196,7 +197,7 @@
 
     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_TRUE(LO_FPS.equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
@@ -289,7 +290,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(
-                    Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+                    Return(Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::Default)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -302,7 +303,7 @@
 
     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_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -311,7 +312,7 @@
     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_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
@@ -321,7 +322,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -335,7 +336,7 @@
     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_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -345,7 +346,7 @@
     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_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
@@ -397,7 +398,8 @@
     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_TRUE(HI_FPS.equalsWithMargin(history().summarize(time)[1].desiredRefreshRate));
+
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -411,7 +413,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -430,7 +432,7 @@
 
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -440,9 +442,9 @@
     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_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
@@ -452,9 +454,9 @@
     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_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -469,7 +471,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -490,7 +492,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
@@ -567,12 +569,12 @@
     EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(Fps(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)));
+                    Layer::FrameRate(Fps(90.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     nsecs_t time = systemTime();
 
@@ -585,7 +587,7 @@
     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_TRUE(Fps(60.0f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 }
@@ -643,7 +645,7 @@
 
     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);
+        recordFramesAndExpect(layer, time, Fps(fps), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
     }
 }
 
@@ -653,13 +655,13 @@
     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, Fps(60.0f), Fps(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);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
 }
 
 TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
@@ -669,11 +671,11 @@
 
     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);
+    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.00f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
 }
 
 class LayerHistoryTestParameterized : public LayerHistoryTest,
@@ -724,7 +726,7 @@
 
             bool max = false;
             bool min = false;
-            float heuristic = 0;
+            Fps heuristic{0.0};
             for (const auto& layer : history().summarize(time)) {
                 if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
                     heuristic = layer.desiredRefreshRate;
@@ -736,7 +738,7 @@
             }
 
             if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
-                EXPECT_FLOAT_EQ(24.0f, heuristic);
+                EXPECT_TRUE(Fps(24.0f).equalsWithMargin(heuristic));
                 EXPECT_FALSE(max);
                 if (history().summarize(time).size() == 2) {
                     EXPECT_TRUE(min);
@@ -750,4 +752,5 @@
                         ::testing::Values(1s, 2s, 3s, 4s, 5s));
 
 } // namespace
-} // namespace android::scheduler
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index f2b7191..83ad737 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -30,6 +30,7 @@
 using testing::_;
 
 namespace android {
+
 namespace scheduler {
 
 namespace hal = android::hardware::graphics::composer::hal;
@@ -43,11 +44,11 @@
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
-    float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+    Fps findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, Fps frameRate) {
         return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
     }
 
-    std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+    std::vector<Fps> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
         return refreshRateConfigs.mKnownFrameRates;
     }
 
@@ -62,29 +63,29 @@
 
     // Test configs
     std::shared_ptr<const HWC2::Display::Config> mConfig60 =
-            createConfig(HWC_CONFIG_ID_60, 0, static_cast<int64_t>(1e9f / 60));
+            createConfig(HWC_CONFIG_ID_60, 0, Fps(60.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90 =
-            createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90));
+            createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentGroup =
-            createConfig(HWC_CONFIG_ID_90, 1, static_cast<int64_t>(1e9f / 90));
+            createConfig(HWC_CONFIG_ID_90, 1, Fps(90.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentResolution =
-            createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90), 111, 222);
+            createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs(), 111, 222);
     std::shared_ptr<const HWC2::Display::Config> mConfig72 =
-            createConfig(HWC_CONFIG_ID_72, 0, static_cast<int64_t>(1e9f / 72));
+            createConfig(HWC_CONFIG_ID_72, 0, Fps(72.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig72DifferentGroup =
-            createConfig(HWC_CONFIG_ID_72, 1, static_cast<int64_t>(1e9f / 72));
+            createConfig(HWC_CONFIG_ID_72, 1, Fps(72.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig120 =
-            createConfig(HWC_CONFIG_ID_120, 0, static_cast<int64_t>(1e9f / 120));
+            createConfig(HWC_CONFIG_ID_120, 0, Fps(120.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig120DifferentGroup =
-            createConfig(HWC_CONFIG_ID_120, 1, static_cast<int64_t>(1e9f / 120));
+            createConfig(HWC_CONFIG_ID_120, 1, Fps(120.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig30 =
-            createConfig(HWC_CONFIG_ID_30, 0, static_cast<int64_t>(1e9f / 30));
+            createConfig(HWC_CONFIG_ID_30, 0, Fps(30.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig30DifferentGroup =
-            createConfig(HWC_CONFIG_ID_30, 1, static_cast<int64_t>(1e9f / 30));
+            createConfig(HWC_CONFIG_ID_30, 1, Fps(30.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig25DifferentGroup =
-            createConfig(HWC_CONFIG_ID_25, 1, static_cast<int64_t>(1e9f / 25));
+            createConfig(HWC_CONFIG_ID_25, 1, Fps(25.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig50 =
-            createConfig(HWC_CONFIG_ID_50, 0, static_cast<int64_t>(1e9f / 50));
+            createConfig(HWC_CONFIG_ID_50, 0, Fps(50.0f).getPeriodNsecs());
 
     // Test device configurations
     // The positions of the configs in the arrays below MUST match their IDs. For example,
@@ -124,23 +125,23 @@
              mConfig50};
 
     // Expected RefreshRate objects
-    RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, "60fps", 60,
+    RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, Fps(60),
                                      RefreshRate::ConstructorTag(0)};
     RefreshRate mExpectedAlmost60Config = {HWC_CONFIG_ID_60,
-                                           createConfig(HWC_CONFIG_ID_60, 0, 16666665), "60fps", 60,
+                                           createConfig(HWC_CONFIG_ID_60, 0, 16666665), Fps(60),
                                            RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, "90fps", 90,
+    RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, Fps(90),
                                      RefreshRate::ConstructorTag(0)};
     RefreshRate mExpected90DifferentGroupConfig = {HWC_CONFIG_ID_90, mConfig90DifferentGroup,
-                                                   "90fps", 90, RefreshRate::ConstructorTag(0)};
+                                                   Fps(90), RefreshRate::ConstructorTag(0)};
     RefreshRate mExpected90DifferentResolutionConfig = {HWC_CONFIG_ID_90,
-                                                        mConfig90DifferentResolution, "90fps", 90,
+                                                        mConfig90DifferentResolution, Fps(90),
                                                         RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, "72fps", 72,
+    RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, Fps(72.0f),
                                      RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, "30fps", 30,
+    RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, Fps(30),
                                      RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, "120fps", 120,
+    RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, Fps(120),
                                       RefreshRate::ConstructorTag(0)};
 
     Hwc2::mock::Display mDisplay;
@@ -192,8 +193,11 @@
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HwcConfigIndexType(10), {60, 60}}), 0);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {20, 40}}), 0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HwcConfigIndexType(10), {Fps(60), Fps(60)}}),
+              0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(20), Fps(40)}}),
+              0);
 }
 
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
@@ -227,7 +231,8 @@
     ASSERT_EQ(mExpected60Config, minRate60);
     ASSERT_EQ(mExpected60Config, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+              0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
     const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
@@ -252,7 +257,8 @@
     ASSERT_EQ(mExpected60Config, minRate60);
     ASSERT_EQ(mExpected60Config, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+              0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
     const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
@@ -274,7 +280,8 @@
     ASSERT_EQ(mExpected60Config, minRate);
     ASSERT_EQ(mExpected90Config, performanceRate);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
 
     auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
     auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
@@ -297,7 +304,8 @@
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
     }
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+              0);
     {
         auto& current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
@@ -315,7 +323,8 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -338,34 +347,35 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     lr.name = "45Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     lr.name = "30Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.name = "24Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.name = "";
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected60Config,
@@ -375,28 +385,30 @@
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HWC_CONFIG_ID_90, {Fps(90.0f), Fps(90.0f)}}),
+              0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected90Config,
@@ -406,28 +418,30 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HWC_CONFIG_ID_60, {Fps(0.0f), Fps(120.0f)}}),
+              0);
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -436,24 +450,24 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -474,24 +488,24 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -506,23 +520,23 @@
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48.0f;
+    lr2.desiredRefreshRate = Fps(48.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48.0f;
+    lr2.desiredRefreshRate = Fps(48.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -538,82 +552,82 @@
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
     lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
     EXPECT_EQ(mExpected90Config,
@@ -636,24 +650,24 @@
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected30Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -676,41 +690,41 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     lr.name = "45Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     lr.name = "30Hz Heuristic";
     EXPECT_EQ(mExpected30Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.name = "24Hz Heuristic";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
     EXPECT_EQ(mExpected72Config,
@@ -736,39 +750,39 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 24.0f;
+    lr2.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 24.0f;
+    lr2.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 15.0f;
+    lr1.desiredRefreshRate = Fps(15.0f);
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 45.0f;
+    lr2.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 45.0f;
+    lr2.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -783,7 +797,7 @@
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = fps;
+        lr.desiredRefreshRate = Fps(fps);
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
         EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -801,33 +815,33 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 90.0f;
+    lr1.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 90.0f;
+    lr1.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
 
 TEST_F(RefreshRateConfigsTest, testInPolicy) {
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(60.000004f, 60.000004f));
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(59.0f, 60.1f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(75.0f, 90.0f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(60.0011f, 90.0f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(50.0f, 59.998f));
+    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(60.000004f), Fps(60.000004f)));
+    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(59.0f), Fps(60.1f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(75.0f), Fps(90.0f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(60.0011f), Fps(90.0f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(50.0f), Fps(59.998f)));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
@@ -840,7 +854,7 @@
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = fps;
+        lr.desiredRefreshRate = Fps(fps);
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
         EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -858,25 +872,25 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -884,16 +898,16 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -912,7 +926,7 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
@@ -920,7 +934,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
@@ -928,7 +942,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -936,7 +950,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -945,10 +959,10 @@
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -972,40 +986,40 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(true, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(false, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(true, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
@@ -1041,7 +1055,7 @@
 
     for (const auto& test : testCases) {
         lr.vote = LayerVoteType::ExplicitDefault;
-        lr.desiredRefreshRate = test.first;
+        lr.desiredRefreshRate = Fps(test.first);
 
         std::stringstream ss;
         ss << "ExplicitDefault " << test.first << " fps";
@@ -1049,7 +1063,7 @@
 
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_FLOAT_EQ(refreshRate.getFps(), test.second)
+        EXPECT_TRUE(refreshRate.getFps().equalsWithMargin(Fps(test.second)))
                 << "Expecting " << test.first << "fps => " << test.second << "Hz";
     }
 }
@@ -1061,7 +1075,7 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_90);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1069,7 +1083,7 @@
 
     RefreshRateConfigs::GlobalSignals consideredSignals;
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
     EXPECT_EQ(mExpected60Config,
@@ -1085,14 +1099,14 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
     EXPECT_EQ(mExpected90Config,
@@ -1106,7 +1120,7 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_90);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     RefreshRateConfigs::GlobalSignals consideredSignals;
@@ -1119,7 +1133,7 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1130,7 +1144,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1141,7 +1155,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Heuristic;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1152,7 +1166,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Max;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Max";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1163,7 +1177,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Min;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Min";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1182,7 +1196,7 @@
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 90.0f;
+    layer.desiredRefreshRate = Fps(90.0f);
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
@@ -1207,7 +1221,7 @@
 
     // Verify that we won't do a seamless switch if we request the same mode as the default
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::OnlySeamless;
     ASSERT_EQ(HWC_CONFIG_ID_90,
@@ -1216,7 +1230,7 @@
 
     // Verify that if the current config is in another group and there are no layers with
     // seamlessness=SeamedAndSeamless we'll go back to the default group.
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::Default;
     ASSERT_EQ(HWC_CONFIG_ID_60,
@@ -1231,7 +1245,7 @@
     layers.push_back(LayerRequirement{.weight = 0.5f});
     auto& layer2 = layers[layers.size() - 1];
     layer2.vote = LayerVoteType::ExplicitDefault;
-    layer2.desiredRefreshRate = 90.0f;
+    layer2.desiredRefreshRate = Fps(90.0f);
     layer2.name = "90Hz ExplicitDefault";
     layer2.seamlessness = Seamlessness::SeamedAndSeamless;
     layer2.focused = false;
@@ -1262,7 +1276,7 @@
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
@@ -1291,13 +1305,13 @@
     auto layers = std::vector<
             LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
                                                .vote = LayerVoteType::ExplicitDefault,
-                                               .desiredRefreshRate = 60.0f,
+                                               .desiredRefreshRate = Fps(60.0f),
                                                .seamlessness = Seamlessness::SeamedAndSeamless,
                                                .weight = 0.5f,
                                                .focused = false},
                               LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
                                                .vote = LayerVoteType::ExplicitExactOrMultiple,
-                                               .desiredRefreshRate = 25.0f,
+                                               .desiredRefreshRate = Fps(25.0f),
                                                .seamlessness = Seamlessness::OnlySeamless,
                                                .weight = 1.0f,
                                                .focused = true}};
@@ -1307,7 +1321,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getConfigId());
 
-    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = 30.0f;
+    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = Fps(30.0f);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_30);
 
     ASSERT_EQ(HWC_CONFIG_ID_25,
@@ -1325,7 +1339,7 @@
 
     // Return the config ID from calling getBestRefreshRate() for a single layer with the
     // given voteType and fps.
-    auto getFrameRate = [&](LayerVoteType voteType, float fps, bool touchActive = false,
+    auto getFrameRate = [&](LayerVoteType voteType, Fps fps, bool touchActive = false,
                             bool focused = true) -> HwcConfigIndexType {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
@@ -1335,43 +1349,44 @@
     };
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {30.f, 60.f}, {30.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(30.f), Fps(60.f)}, {Fps(30.f), Fps(90.f)}}),
               0);
     EXPECT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false})
                       .getConfigId());
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
 
     // Layers not focused are not allowed to override primary config
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/false,
+              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/false,
                            /*focused=*/false));
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/false,
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/false,
                            /*focused=*/false));
 
     // Touch boost should be restricted to the primary range.
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f, /*touch=*/true));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f), /*touch=*/true));
     // When we're higher than the primary range max due to a layer frame rate setting, touch boost
     // shouldn't drag us back down to the primary range max.
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/true));
+    EXPECT_EQ(HWC_CONFIG_ID_90,
+              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/true));
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/true));
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/true));
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 60.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(60.f)}}),
               0);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
 }
 
 TEST_F(RefreshRateConfigsTest, idle) {
@@ -1385,7 +1400,7 @@
     const auto getIdleFrameRate = [&](LayerVoteType voteType,
                                       bool touchActive) -> HwcConfigIndexType {
         layers[0].vote = voteType;
-        layers[0].desiredRefreshRate = 90.f;
+        layers[0].desiredRefreshRate = Fps(90.f);
         RefreshRateConfigs::GlobalSignals consideredSignals;
         const auto configId =
                 refreshRateConfigs
@@ -1398,7 +1413,7 @@
     };
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     // Idle should be lower priority than touch boost.
@@ -1439,22 +1454,22 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
-        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
-        float expectedFrameRate;
+        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, Fps(fps));
+        Fps expectedFrameRate;
         if (fps < 26.91f) {
-            expectedFrameRate = 24.0f;
+            expectedFrameRate = Fps(24.0f);
         } else if (fps < 37.51f) {
-            expectedFrameRate = 30.0f;
+            expectedFrameRate = Fps(30.0f);
         } else if (fps < 52.51f) {
-            expectedFrameRate = 45.0f;
+            expectedFrameRate = Fps(45.0f);
         } else if (fps < 66.01f) {
-            expectedFrameRate = 60.0f;
+            expectedFrameRate = Fps(60.0f);
         } else if (fps < 81.01f) {
-            expectedFrameRate = 72.0f;
+            expectedFrameRate = Fps(72.0f);
         } else {
-            expectedFrameRate = 90.0f;
+            expectedFrameRate = Fps(90.0f);
         }
-        EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+        EXPECT_TRUE(expectedFrameRate.equalsWithMargin(knownFrameRate))
                 << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
     }
 }
@@ -1465,26 +1480,27 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     struct ExpectedRate {
-        float rate;
+        Fps rate;
         const RefreshRate& expected;
     };
 
     /* clang-format off */
     std::vector<ExpectedRate> knownFrameRatesExpectations = {
-        {24.0f, mExpected60Config},
-        {30.0f, mExpected60Config},
-        {45.0f, mExpected90Config},
-        {60.0f, mExpected60Config},
-        {72.0f, mExpected90Config},
-        {90.0f, mExpected90Config},
+        {Fps(24.0f), mExpected60Config},
+        {Fps(30.0f), mExpected60Config},
+        {Fps(45.0f), mExpected90Config},
+        {Fps(60.0f), mExpected60Config},
+        {Fps(72.0f), mExpected90Config},
+        {Fps(90.0f), mExpected90Config},
     };
     /* clang-format on */
 
     // Make sure the test tests all the known frame rate
     const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
-    const auto equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
-                                  knownFrameRatesExpectations.begin(),
-                                  [](float a, const ExpectedRate& b) { return a == b.rate; });
+    const auto equal =
+            std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+                       knownFrameRatesExpectations.begin(),
+                       [](Fps a, const ExpectedRate& b) { return a.equalsWithMargin(b.rate); });
     EXPECT_TRUE(equal);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1514,15 +1530,18 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(60, 90), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(90)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => NoChange, avoid extra calls.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::NoChange, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(90, 90), current 90Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
 }
 
@@ -1554,7 +1573,7 @@
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
 
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
-    refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.5});
+    refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.5f});
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
     refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.6f});
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index d0bb9e2..e93d0d0 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -14,10 +14,6 @@
  * 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 "SchedulerUnittests"
 
@@ -25,6 +21,8 @@
 #include <log/log.h>
 #include <thread>
 
+#include "Scheduler/HwcStrongTypes.h"
+#include "Scheduler/RefreshRateConfigs.h"
 #include "Scheduler/RefreshRateStats.h"
 #include "mock/DisplayHardware/MockDisplay.h"
 #include "mock/MockTimeStats.h"
@@ -51,8 +49,9 @@
     void init(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
         mRefreshRateConfigs =
                 std::make_unique<RefreshRateConfigs>(configs, /*currentConfig=*/CONFIG_ID_0);
-        mRefreshRateStats = std::make_unique<RefreshRateStats>(*mRefreshRateConfigs, mTimeStats,
-                                                               /*currentConfigId=*/CONFIG_ID_0,
+
+        const auto currFps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, currFps,
                                                                /*currentPowerMode=*/PowerMode::OFF);
     }
 
@@ -80,8 +79,8 @@
 
 std::shared_ptr<const HWC2::Display::Config> RefreshRateStatsTest::createConfig(
         HwcConfigIndexType configId, int32_t configGroup, int64_t vsyncPeriod) {
-    return HWC2::Display::Config::Builder(mDisplay, configId.value())
-            .setVsyncPeriod(int32_t(vsyncPeriod))
+    return HWC2::Display::Config::Builder(mDisplay, static_cast<hal::HWConfigId>(configId.value()))
+            .setVsyncPeriod(static_cast<int32_t>(vsyncPeriod))
             .setConfigGroup(configGroup)
             .build();
 }
@@ -102,7 +101,7 @@
     // Setting up tests on mobile harness can be flaky with time passing, so testing for
     // exact time changes can result in flaxy numbers. To avoid that remember old
     // numbers to make sure the correct values are increasing in the next test.
-    int screenOff = times["ScreenOff"];
+    auto screenOff = times["ScreenOff"];
 
     // Screen is off by default.
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -110,7 +109,8 @@
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(0u, times.count("90.00fps"));
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+    const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+    mRefreshRateStats->setRefreshRate(config0Fps);
     mRefreshRateStats->setPowerMode(PowerMode::ON);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -120,13 +120,13 @@
     EXPECT_LT(0, times["90.00fps"]);
 
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
-    int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+    auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(ninety, times["90.00fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+    mRefreshRateStats->setRefreshRate(config0Fps);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -150,14 +150,16 @@
     // Setting up tests on mobile harness can be flaky with time passing, so testing for
     // exact time changes can result in flaxy numbers. To avoid that remember old
     // numbers to make sure the correct values are increasing in the next test.
-    int screenOff = times["ScreenOff"];
+    auto screenOff = times["ScreenOff"];
 
     // Screen is off by default.
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+    const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+    const auto config1Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_1).getFps();
+    mRefreshRateStats->setRefreshRate(config0Fps);
     mRefreshRateStats->setPowerMode(PowerMode::ON);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -167,8 +169,8 @@
     EXPECT_LT(0, times["90.00fps"]);
 
     // When power mode is normal, time for configs updates.
-    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
-    int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+    mRefreshRateStats->setRefreshRate(config1Fps);
+    auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
@@ -176,15 +178,15 @@
     ASSERT_EQ(1u, times.count("60.00fps"));
     EXPECT_LT(0, times["60.00fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
-    int sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
+    mRefreshRateStats->setRefreshRate(config0Fps);
+    auto sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
     EXPECT_LT(ninety, times["90.00fps"]);
     EXPECT_EQ(sixty, times["60.00fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
+    mRefreshRateStats->setRefreshRate(config1Fps);
     ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -195,7 +197,7 @@
     // Because the power mode is not PowerMode::ON, switching the config
     // does not update refresh rates that come from the config.
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
-    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+    mRefreshRateStats->setRefreshRate(config0Fps);
     sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -203,7 +205,7 @@
     EXPECT_EQ(ninety, times["90.00fps"]);
     EXPECT_EQ(sixty, times["60.00fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
+    mRefreshRateStats->setRefreshRate(config1Fps);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -214,6 +216,3 @@
 } // namespace
 } // namespace scheduler
 } // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 5278641..c47b141 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -108,11 +108,12 @@
  */
 class SetFrameRateTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
 protected:
-    const FrameRate FRAME_RATE_VOTE1 = FrameRate(67.f, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE2 = FrameRate(14.f, FrameRateCompatibility::ExactOrMultiple);
-    const FrameRate FRAME_RATE_VOTE3 = FrameRate(99.f, FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_TREE = FrameRate(0, FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(0, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE1 = FrameRate(Fps(67.f), FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE2 =
+            FrameRate(Fps(14.f), FrameRateCompatibility::ExactOrMultiple);
+    const FrameRate FRAME_RATE_VOTE3 = FrameRate(Fps(99.f), FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(0.f), FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(0.f), FrameRateCompatibility::Default);
 
     SetFrameRateTest();
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index c0de465..030073c 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -223,12 +223,14 @@
                                          .build());
         }
 
-        mFlinger->mRefreshRateConfigs = std::make_unique<
-                scheduler::RefreshRateConfigs>(configs, /*currentConfig=*/HwcConfigIndexType(0));
-        mFlinger->mRefreshRateStats = std::make_unique<
-                scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs, *mFlinger->mTimeStats,
-                                             /*currentConfig=*/HwcConfigIndexType(0),
-                                             /*powerMode=*/hal::PowerMode::OFF);
+        const auto currConfig = HwcConfigIndexType(0);
+        mFlinger->mRefreshRateConfigs =
+                std::make_unique<scheduler::RefreshRateConfigs>(configs, currConfig);
+        const auto currFps =
+                mFlinger->mRefreshRateConfigs->getRefreshRateFromConfigId(currConfig).getFps();
+        mFlinger->mRefreshRateStats =
+                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
+                                                              /*powerMode=*/hal::PowerMode::OFF);
         mFlinger->mVsyncConfiguration =
                 mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
         mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
diff --git a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
index 72ee6db..2a35f69 100644
--- a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
@@ -29,17 +29,18 @@
 
 class TestableWorkDuration : public impl::WorkDuration {
 public:
-    TestableWorkDuration(float currentFps, nsecs_t sfDuration, nsecs_t appDuration,
+    TestableWorkDuration(Fps currentFps, nsecs_t sfDuration, nsecs_t appDuration,
                          nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
                          nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
-          : impl::WorkDuration({60.0f, 90.0f}, currentFps, sfDuration, appDuration, sfEarlyDuration,
-                               appEarlyDuration, sfEarlyGlDuration, appEarlyGlDuration) {}
+          : impl::WorkDuration({Fps(60.0f), Fps(90.0f)}, currentFps, sfDuration, appDuration,
+                               sfEarlyDuration, appEarlyDuration, sfEarlyGlDuration,
+                               appEarlyGlDuration) {}
 };
 
 class WorkDurationTest : public testing::Test {
 protected:
     WorkDurationTest()
-          : mWorkDuration(60.0f, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
+          : mWorkDuration(Fps(60.0f), 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
                           21'000'000) {}
 
     ~WorkDurationTest() = default;
@@ -51,9 +52,9 @@
  * Test cases
  */
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_60Hz) {
-    mWorkDuration.setRefreshRateFps(60.0f);
+    mWorkDuration.setRefreshRateFps(Fps(60.0f));
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(60.0f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 6'166'667);
@@ -76,9 +77,9 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_90Hz) {
-    mWorkDuration.setRefreshRateFps(90.0f);
+    mWorkDuration.setRefreshRateFps(Fps(90.0f));
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(90.0f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 611'111);
@@ -101,7 +102,7 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_DefaultOffsets) {
-    TestableWorkDuration phaseOffsetsWithDefaultValues(60.0f, -1, -1, -1, -1, -1, -1);
+    TestableWorkDuration phaseOffsetsWithDefaultValues(Fps(60.0f), -1, -1, -1, -1, -1, -1);
 
     auto validateOffsets = [](const auto& offsets, std::chrono::nanoseconds vsyncPeriod) {
         EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
@@ -123,21 +124,20 @@
         EXPECT_EQ(offsets.earlyGpu.appWorkDuration, vsyncPeriod);
     };
 
-    const auto testForRefreshRate = [&](float refreshRate) {
+    const auto testForRefreshRate = [&](Fps refreshRate) {
         phaseOffsetsWithDefaultValues.setRefreshRateFps(refreshRate);
         auto currentOffsets = phaseOffsetsWithDefaultValues.getCurrentConfigs();
         auto offsets = phaseOffsetsWithDefaultValues.getConfigsForRefreshRate(refreshRate);
         EXPECT_EQ(currentOffsets, offsets);
-        validateOffsets(offsets,
-                        std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / refreshRate)));
+        validateOffsets(offsets, std::chrono::nanoseconds(refreshRate.getPeriodNsecs()));
     };
 
-    testForRefreshRate(90.0f);
-    testForRefreshRate(60.0f);
+    testForRefreshRate(Fps(90.0f));
+    testForRefreshRate(Fps(60.0f));
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(14.7f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(14.7f));
 
     EXPECT_EQ(offsets.late.sfOffset, 57'527'208);
     EXPECT_EQ(offsets.late.appOffset, 37'027'208);
@@ -171,9 +171,9 @@
                          std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
                          std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
                          nsecs_t thresholdForNextVsync)
-          : impl::PhaseOffsets({60.0f, 90.0f}, 60.0f, vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
-                               earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
-                               earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
+          : impl::PhaseOffsets({Fps(60.0f), Fps(90.0f)}, Fps(60.0f), vsyncPhaseOffsetNs,
+                               sfVSyncPhaseOffsetNs, earlySfOffsetNs, earlyGpuSfOffsetNs,
+                               earlyAppOffsetNs, earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
                                highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
                                highFpsEarlyGpuSfOffsetNs, highFpsEarlyAppOffsetNs,
                                highFpsEarlyGpuAppOffsetNs, thresholdForNextVsync) {}
@@ -190,7 +190,7 @@
 };
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(14.7f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(14.7f));
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -212,7 +212,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_60Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(60.0f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -234,7 +234,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_90Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(90.0f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -258,7 +258,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_60Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(60.0f);
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 1'000'000);
@@ -282,7 +282,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_90Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(90.0f);
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 75228d6..c18bf18 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -19,6 +19,7 @@
         "VibratorCallbackScheduler.cpp",
         "VibratorHalController.cpp",
         "VibratorHalWrapper.cpp",
+        "VibratorManagerHalController.cpp",
         "VibratorManagerHalWrapper.cpp",
     ],
 
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index b24e5c4..6bf6581 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -26,13 +26,13 @@
 
 namespace vibrator {
 
-std::shared_ptr<ManagerHalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
+std::shared_ptr<ManagerHalWrapper> connectManagerHal(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);
+            return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), hal);
         }
     }
 
@@ -80,12 +80,11 @@
 
 // -------------------------------------------------------------------------------------------------
 
-bool ManagerHalController::init() {
+void ManagerHalController::init() {
     std::lock_guard<std::mutex> lock(mConnectedHalMutex);
     if (mConnectedHal == nullptr) {
         mConnectedHal = mConnector(mCallbackScheduler);
     }
-    return mConnectedHal != nullptr;
 }
 
 HalResult<void> ManagerHalController::ping() {
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
index cf82562..9168565 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -19,6 +19,7 @@
 
 #include <android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorManagerHalWrapper.h>
 #include <unordered_map>
 
 namespace android {
@@ -36,7 +37,7 @@
     ManagerHalController()
           : ManagerHalController(std::make_shared<CallbackScheduler>(), &connectManagerHal) {}
     ManagerHalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
-          : mConnector(connector), mConnectedHal(nullptr) {}
+          : mConnector(connector), mCallbackScheduler(callbackScheduler), mConnectedHal(nullptr) {}
     virtual ~ManagerHalController() = default;
 
     /* Connects to the HAL service, possibly waiting for the registered service to
@@ -64,9 +65,10 @@
 
 private:
     Connector mConnector;
+    std::shared_ptr<CallbackScheduler> mCallbackScheduler;
     std::mutex mConnectedHalMutex;
     // Shared pointer to allow local copies to be used by different threads.
-    std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
+    std::shared_ptr<ManagerHalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
 
     template <typename T>
     HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 6801f76..9af1b7b 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",
+        "VibratorManagerHalControllerTest.cpp",
         "VibratorManagerHalWrapperAidlTest.cpp",
         "VibratorManagerHalWrapperLegacyTest.cpp",
     ],
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
index 3b036ee..e5fbbae 100644
--- a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -22,22 +22,28 @@
 
 #include <utils/Log.h>
 
-#include <vibratorservice/VibratorManagerHalWrapper.h>
+#include <vibratorservice/VibratorManagerHalController.h>
 
 #include "test_utils.h"
 
+using android::vibrator::HalController;
+
 using namespace android;
 using namespace testing;
 
 static constexpr int MAX_ATTEMPTS = 2;
+static const std::vector<int32_t> VIBRATOR_IDS = {1, 2};
+static constexpr int VIBRATOR_ID = 1;
 
 class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
 public:
+    MOCK_METHOD(void, tryReconnect, (), (override));
     MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
-    MOCK_METHOD(vibrator::HalResult<int32_t>, getCapabilities, (), (override));
+    MOCK_METHOD(vibrator::HalResult<vibrator::ManagerCapabilities>, 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<std::shared_ptr<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,
@@ -50,13 +56,13 @@
     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;
-                                         });
+        mMockHal = std::make_shared<StrictMock<MockManagerHalWrapper>>();
+        auto connector = [this](std::shared_ptr<vibrator::CallbackScheduler>) {
+            android_atomic_inc(&mConnectCounter);
+            return mMockHal;
+        };
+        mController = std::make_unique<vibrator::ManagerHalController>(std::move(callbackScheduler),
+                                                                       connector);
         ASSERT_NE(mController, nullptr);
     }
 
@@ -65,8 +71,7 @@
     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,
+    void setHalExpectations(int32_t cardinality, vibrator::HalResult<void> voidResult,
                             vibrator::HalResult<vibrator::ManagerCapabilities> capabilitiesResult,
                             vibrator::HalResult<std::vector<int32_t>> idsResult,
                             vibrator::HalResult<std::shared_ptr<HalController>> vibratorResult) {
@@ -100,24 +105,20 @@
 };
 
 TEST_F(VibratorManagerHalControllerTest, TestInit) {
-    ASSERT_TRUE(mController->init());
+    mController->init();
     ASSERT_EQ(1, mConnectCounter);
 
     // Noop when wrapper was already initialized.
-    ASSERT_TRUE(mController->init());
+    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(),
+    setHalExpectations(/* cardinality= */ 1, 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));
+                       vibrator::HalResult<std::vector<int32_t>>::ok(VIBRATOR_IDS),
+                       vibrator::HalResult<std::shared_ptr<HalController>>::ok(nullptr));
 
     ASSERT_TRUE(mController->ping().isOk());
 
@@ -127,13 +128,13 @@
 
     auto getVibratorIdsResult = mController->getVibratorIds();
     ASSERT_TRUE(getVibratorIdsResult.isOk());
-    ASSERT_EQ(ids, getVibratorIdsResult.value());
+    ASSERT_EQ(VIBRATOR_IDS, getVibratorIdsResult.value());
 
-    auto getVibratorResult = mController->getVibrator(1);
+    auto getVibratorResult = mController->getVibrator(VIBRATOR_ID);
     ASSERT_TRUE(getVibratorResult.isOk());
     ASSERT_EQ(nullptr, getVibratorResult.value());
 
-    ASSERT_TRUE(mController->prepareSynced(ids).isOk());
+    ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isOk());
     ASSERT_TRUE(mController->triggerSynced([]() {}).isOk());
     ASSERT_TRUE(mController->cancelSynced().isOk());
 
@@ -141,20 +142,18 @@
 }
 
 TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
-    std::vector<int32_t> ids;
-    setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::unsupported(),
+    setHalExpectations(/* cardinality= */ 1, vibrator::HalResult<void>::unsupported(),
                        vibrator::HalResult<vibrator::ManagerCapabilities>::unsupported(),
                        vibrator::HalResult<std::vector<int32_t>>::unsupported(),
-                       vibrator::HalResult<
-                               std::shared_ptr<vibrator::HalController>>::unsupported());
+                       vibrator::HalResult<std::shared_ptr<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->getVibrator(VIBRATOR_ID).isUnsupported());
+    ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isUnsupported());
     ASSERT_TRUE(mController->triggerSynced([]() {}).isUnsupported());
     ASSERT_TRUE(mController->cancelSynced().isUnsupported());
 
@@ -162,20 +161,18 @@
 }
 
 TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultResetsHalConnection) {
-    std::vector<int32_t> ids;
-    setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::failed("message"),
+    setHalExpectations(MAX_ATTEMPTS, 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"));
+                       vibrator::HalResult<std::shared_ptr<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->getVibrator(VIBRATOR_ID).isFailed());
+    ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isFailed());
     ASSERT_TRUE(mController->triggerSynced([]() {}).isFailed());
     ASSERT_TRUE(mController->cancelSynced().isFailed());