Merge "SurfaceFlinger: relative size RefreshRateOverlay"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 20bfe65..4a84884 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -14,10 +14,12 @@
                libs/renderengine/
                libs/ui/
                libs/vr/
+               opengl/libs/
                services/bufferhub/
                services/inputflinger/
                services/surfaceflinger/
                services/vr/
+               vulkan/
 
 [Hook Scripts]
 owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index 08a31c1..402767a 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -15,6 +15,7 @@
 cc_defaults {
     name: "idlcli-defaults",
     shared_libs: [
+        "android.hardware.vibrator-ndk_platform",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
@@ -24,7 +25,6 @@
         "libhidlbase",
         "liblog",
         "libutils",
-        "vintf-vibrator-ndk_platform",
     ],
     cflags: [
         "-DLOG_TAG=\"idlcli\"",
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index f8a68b4..6b14bee 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -639,10 +639,8 @@
                 if (delete_dir_contents(path, true) != 0) {
                     res = error("Failed to delete contents of " + path);
                 }
-                path = StringPrintf("%s/Android/obb/%s", extPath.c_str(), pkgname);
-                if (delete_dir_contents(path, true) != 0) {
-                    res = error("Failed to delete contents of " + path);
-                }
+                // Note that we explicitly don't delete OBBs - those are only removed on
+                // app uninstall.
             }
         }
     }
diff --git a/cmds/installd/QuotaUtils.cpp b/cmds/installd/QuotaUtils.cpp
index a71e01c..e080291 100644
--- a/cmds/installd/QuotaUtils.cpp
+++ b/cmds/installd/QuotaUtils.cpp
@@ -101,6 +101,26 @@
     }
 }
 
+int64_t GetOccupiedSpaceForProjectId(const std::string& uuid, int projectId) {
+    const std::string device = FindQuotaDeviceForUuid(uuid);
+    if (device == "") {
+        return -1;
+    }
+    struct dqblk dq;
+    if (quotactl(QCMD(Q_GETQUOTA, PRJQUOTA), device.c_str(), projectId,
+            reinterpret_cast<char*>(&dq)) != 0) {
+        if (errno != ESRCH) {
+            PLOG(ERROR) << "Failed to quotactl " << device << " for Project ID " << projectId;
+        }
+        return -1;
+    } else {
+#if MEASURE_DEBUG
+        LOG(DEBUG) << "quotactl() for Project ID " << projectId << " " << dq.dqb_curspace;
+#endif
+        return dq.dqb_curspace;
+    }
+}
+
 int64_t GetOccupiedSpaceForGid(const std::string& uuid, gid_t gid) {
     const std::string device = FindQuotaDeviceForUuid(uuid);
     if (device == "") {
diff --git a/cmds/installd/QuotaUtils.h b/cmds/installd/QuotaUtils.h
index 9ad170f..96aca04 100644
--- a/cmds/installd/QuotaUtils.h
+++ b/cmds/installd/QuotaUtils.h
@@ -35,6 +35,8 @@
 /* Get the current occupied space in bytes for a gid or -1 if fails */
 int64_t GetOccupiedSpaceForGid(const std::string& uuid, gid_t gid);
 
+/* Get the current occupied space in bytes for a project id or -1 if fails */
+int64_t GetOccupiedSpaceForProjectId(const std::string& uuid, int projectId);
 }  // namespace installd
 }  // namespace android
 
diff --git a/data/etc/android.hardware.telephony.cdma.xml b/data/etc/android.hardware.telephony.cdma.xml
index 082378d..b598f68 100644
--- a/data/etc/android.hardware.telephony.cdma.xml
+++ b/data/etc/android.hardware.telephony.cdma.xml
@@ -18,4 +18,5 @@
 <permissions>
     <feature name="android.hardware.telephony" />
     <feature name="android.hardware.telephony.cdma" />
+    <feature name="android.hardware.telephony.data" />
 </permissions>
diff --git a/data/etc/android.hardware.telephony.gsm.xml b/data/etc/android.hardware.telephony.gsm.xml
index 7927fa8..fe8a5cf 100644
--- a/data/etc/android.hardware.telephony.gsm.xml
+++ b/data/etc/android.hardware.telephony.gsm.xml
@@ -18,4 +18,5 @@
 <permissions>
     <feature name="android.hardware.telephony" />
     <feature name="android.hardware.telephony.gsm" />
+    <feature name="android.hardware.telephony.data" />
 </permissions>
diff --git a/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png
new file mode 100644
index 0000000..b6c0765
--- /dev/null
+++ b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png
Binary files differ
diff --git a/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png
new file mode 100644
index 0000000..4e975e5
--- /dev/null
+++ b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png
Binary files differ
diff --git a/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png
new file mode 100644
index 0000000..a331095
--- /dev/null
+++ b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png
Binary files differ
diff --git a/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png
new file mode 100644
index 0000000..41e6668
--- /dev/null
+++ b/docs/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png
Binary files differ
diff --git a/libs/adbd_auth/adbd_auth.cpp b/libs/adbd_auth/adbd_auth.cpp
index 6479109..a9c2311 100644
--- a/libs/adbd_auth/adbd_auth.cpp
+++ b/libs/adbd_auth/adbd_auth.cpp
@@ -178,6 +178,10 @@
 
           this->callbacks_.key_authorized(arg, id);
           this->dispatched_prompt_ = std::nullopt;
+
+          // We need to dispatch pending prompts here upon success as well,
+          // since we might have multiple queued prompts.
+          DispatchPendingPrompt();
         } else if (packet[0] == 'N' && packet[1] == 'O') {
           CHECK_EQ(2UL, packet.length());
           // TODO: Do we want a callback if the key is denied?
diff --git a/libs/android_runtime_lazy/android_runtime_lazy.cpp b/libs/android_runtime_lazy/android_runtime_lazy.cpp
index 98d8e8a..8062be6 100644
--- a/libs/android_runtime_lazy/android_runtime_lazy.cpp
+++ b/libs/android_runtime_lazy/android_runtime_lazy.cpp
@@ -15,6 +15,7 @@
  */
 #define LOG_TAG "ANDROID_RUNTIME_LAZY"
 #include "android_runtime/AndroidRuntime.h"
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
 #include <dlfcn.h>
@@ -28,12 +29,18 @@
 std::once_flag loadFlag;
 
 typedef JNIEnv* (*getJNIEnv_t)();
+
+// android_util_Binder.h
 typedef sp<IBinder> (*ibinderForJavaObject_t)(JNIEnv* env, jobject obj);
 typedef jobject (*javaObjectForIBinder_t)(JNIEnv* env, const sp<IBinder>& val);
 
+// android_os_Parcel.h
+typedef Parcel* (*parcelForJavaObject_t)(JNIEnv* env, jobject obj);
+
 getJNIEnv_t _getJNIEnv;
 ibinderForJavaObject_t _ibinderForJavaObject;
 javaObjectForIBinder_t _javaObjectForIBinder;
+parcelForJavaObject_t _parcelForJavaObject;
 
 void load() {
     std::call_once(loadFlag, []() {
@@ -64,6 +71,13 @@
             ALOGW("Could not find javaObjectForIBinder.");
             // no return
         }
+
+        _parcelForJavaObject = reinterpret_cast<parcelForJavaObject_t>(
+            dlsym(handle, "_ZN7android19parcelForJavaObjectEP7_JNIEnvP8_jobject"));
+        if (_parcelForJavaObject == nullptr) {
+            ALOGW("Could not find parcelForJavaObject.");
+            // no return
+        }
     });
 }
 
@@ -95,4 +109,12 @@
     return _javaObjectForIBinder(env, val);
 }
 
+Parcel* parcelForJavaObject(JNIEnv* env, jobject obj) {
+    load();
+    if (_parcelForJavaObject == nullptr) {
+        return nullptr;
+    }
+    return _parcelForJavaObject(env, obj);
+}
+
 } // namespace android
diff --git a/libs/android_runtime_lazy/include/android_os_Parcel.h b/libs/android_runtime_lazy/include/android_os_Parcel.h
new file mode 100644
index 0000000..19b094d
--- /dev/null
+++ b/libs/android_runtime_lazy/include/android_os_Parcel.h
@@ -0,0 +1,30 @@
+/*
+ * 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 <binder/Parcel.h>
+#include "jni.h"
+
+namespace android {
+
+// The name of this file is same with the file in frameworks/base/core/jni/
+// This is intentional to make the client use these exported functions
+// in the same way with the original.
+
+Parcel* parcelForJavaObject(JNIEnv* env, jobject obj);
+
+} // namespace android
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index fa07d04..b37db43 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -50,6 +50,7 @@
         "ibinder.cpp",
         "ibinder_jni.cpp",
         "parcel.cpp",
+        "parcel_jni.cpp",
         "process.cpp",
         "stability.cpp",
         "status.cpp",
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder_jni.h b/libs/binder/ndk/include_ndk/android/binder_ibinder_jni.h
index be3029c..cd1ff1f 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder_jni.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder_jni.h
@@ -36,13 +36,13 @@
 /**
  * Converts an android.os.IBinder object into an AIBinder* object.
  *
- * If either env or the binder is null, null is returned. If this binder object was originally an
+ * If the binder is null, null is returned. If this binder object was originally an
  * AIBinder object, the original object is returned. The returned object has one refcount
  * associated with it, and so this should be accompanied with an AIBinder_decStrong call.
  *
  * Available since API level 29.
  *
- * \param env Java environment.
+ * \param env Java environment. Must not be null.
  * \param binder android.os.IBinder java object.
  *
  * \return an AIBinder object representing the Java binder object. If either parameter is null, or
@@ -54,12 +54,12 @@
 /**
  * Converts an AIBinder* object into an android.os.IBinder object.
  *
- * If either env or the binder is null, null is returned. If this binder object was originally an
- * IBinder object, the original java object will be returned.
+ * If the binder is null, null is returned. If this binder object was originally an IBinder object,
+ * the original java object will be returned.
  *
  * Available since API level 29.
  *
- * \param env Java environment.
+ * \param env Java environment. Must not be null.
  * \param binder the object to convert.
  *
  * \return an android.os.IBinder object or null if the parameters were null.
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel_jni.h b/libs/binder/ndk/include_ndk/android/binder_parcel_jni.h
new file mode 100644
index 0000000..65e1704
--- /dev/null
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel_jni.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+/**
+ * @addtogroup NdkBinder
+ * @{
+ */
+
+/**
+ * @file binder_parcel_jni.h
+ * @brief Conversions between AParcel and android.os.Parcel
+ */
+
+#pragma once
+
+#include <android/binder_parcel.h>
+
+#include <jni.h>
+
+__BEGIN_DECLS
+#if __ANDROID_API__ >= 30
+
+/**
+ * Converts an android.os.Parcel object into an AParcel* object.
+ *
+ * If the parcel is null, null is returned.
+ *
+ * Available since API level 30.
+ *
+ * \param env Java environment. Must not be null.
+ * \param parcel android.os.Parcel java object.
+ *
+ * \return an AParcel object representing the Java parcel object. If either parameter is null, this
+ * will return null. This must be deleted with AParcel_delete. This does not take ownership of the
+ * jobject and is only good for as long as the jobject is alive.
+ */
+__attribute__((warn_unused_result)) AParcel* AParcel_fromJavaParcel(JNIEnv* env, jobject parcel)
+        __INTRODUCED_IN(30);
+
+#endif  //__ANDROID_API__ >= 30
+__END_DECLS
+
+/** @} */
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 71d8103..f3158d7 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -105,6 +105,7 @@
     AIBinder_setExtension;
     AStatus_getDescription;
     AStatus_deleteDescription;
+    AParcel_fromJavaParcel;
 
     AIBinder_markSystemStability; # apex
     AIBinder_markVendorStability; # llndk
diff --git a/libs/binder/ndk/parcel_jni.cpp b/libs/binder/ndk/parcel_jni.cpp
new file mode 100644
index 0000000..53b2d7c
--- /dev/null
+++ b/libs/binder/ndk/parcel_jni.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_parcel_jni.h>
+#include "parcel_internal.h"
+
+#include <android_os_Parcel.h>
+
+using ::android::Parcel;
+using ::android::parcelForJavaObject;
+
+AParcel* AParcel_fromJavaParcel(JNIEnv* env, jobject jbinder) {
+    if (jbinder == nullptr) {
+        return nullptr;
+    }
+
+    Parcel* parcel = parcelForJavaObject(env, jbinder);
+
+    if (parcel == nullptr) {
+        return nullptr;
+    }
+
+    return new AParcel(nullptr /*binder*/, parcel, false /*shouldOwn*/);
+}
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index e1693cf..00f7484 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -33,16 +33,16 @@
     ],
 
     shared_libs: [
+        "android.hardware.graphics.common-ndk_platform",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
         "liblog",
-        "vintf-graphics-common-ndk_platform",
     ],
 
     export_shared_lib_headers: [
+        "android.hardware.graphics.common-ndk_platform",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
-        "vintf-graphics-common-ndk_platform",
     ],
 
     export_include_dirs: [
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 354703b..f07c231 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -145,6 +145,12 @@
 void GraphicsEnv::hintActivityLaunch() {
     ATRACE_CALL();
 
+    {
+        std::lock_guard<std::mutex> lock(mStatsLock);
+        if (mActivityLaunched) return;
+        mActivityLaunched = true;
+    }
+
     std::thread trySendGpuStatsThread([this]() {
         // If there's already graphics driver preloaded in the process, just send
         // the stats info to GpuStats directly through async binder.
@@ -228,12 +234,11 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mStatsLock);
-    const bool doNotSend = mGpuStats.appPackageName.empty();
     if (api == GpuStatsInfo::Api::API_GL) {
-        if (doNotSend) mGpuStats.glDriverToSend = true;
+        mGpuStats.glDriverToSend = true;
         mGpuStats.glDriverLoadingTime = driverLoadingTime;
     } else {
-        if (doNotSend) mGpuStats.vkDriverToSend = true;
+        mGpuStats.vkDriverToSend = true;
         mGpuStats.vkDriverLoadingTime = driverLoadingTime;
     }
 
@@ -250,10 +255,18 @@
     return interface_cast<IGpuService>(binder);
 }
 
+bool GraphicsEnv::readyToSendGpuStatsLocked() {
+    // Only send stats for processes having at least one activity launched and that process doesn't
+    // skip the GraphicsEnvironment setup.
+    return mActivityLaunched && !mGpuStats.appPackageName.empty();
+}
+
 void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t value) {
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mStatsLock);
+    if (!readyToSendGpuStatsLocked()) return;
+
     const sp<IGpuService> gpuService = getGpuService();
     if (gpuService) {
         gpuService->setTargetStats(mGpuStats.appPackageName, mGpuStats.driverVersionCode, stats,
@@ -265,8 +278,7 @@
                                      int64_t driverLoadingTime) {
     ATRACE_CALL();
 
-    // Do not sendGpuStats for those skipping the GraphicsEnvironment setup
-    if (mGpuStats.appPackageName.empty()) return;
+    if (!readyToSendGpuStatsLocked()) return;
 
     ALOGV("sendGpuStats:\n"
           "\tdriverPackageName[%s]\n"
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index c6dc1f8..2219074 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -131,6 +131,8 @@
     void updateUseAngle();
     // Link updatable driver namespace with llndk and vndk-sp libs.
     bool linkDriverNamespaceLocked(android_namespace_t* vndkNamespace);
+    // Check whether this process is ready to send stats.
+    bool readyToSendGpuStatsLocked();
     // Send the initial complete GpuStats to GpuService.
     void sendGpuStatsLocked(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime);
 
@@ -141,6 +143,8 @@
     std::string mSphalLibraries;
     // This mutex protects mGpuStats and get gpuservice call.
     std::mutex mStatsLock;
+    // Cache the activity launch info
+    bool mActivityLaunched = false;
     // Information bookkept for GpuStats.
     GpuStatsInfo mGpuStats;
     // Path to ANGLE libs.
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 54f383e..8af1a1c 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -36,7 +36,10 @@
 DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
                                                ISurfaceComposer::VsyncSource vsyncSource,
                                                ISurfaceComposer::ConfigChanged configChanged)
-      : mLooper(looper), mReceiver(vsyncSource, configChanged), mWaitingForVsync(false) {
+      : mLooper(looper),
+        mReceiver(vsyncSource, configChanged),
+        mWaitingForVsync(false),
+        mConfigChangeFlag(configChanged) {
     ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
 }
 
@@ -47,17 +50,20 @@
         return result;
     }
 
-    int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
-    if (rc < 0) {
-        return UNKNOWN_ERROR;
+    if (mLooper != nullptr) {
+        int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
+        if (rc < 0) {
+            return UNKNOWN_ERROR;
+        }
     }
+
     return OK;
 }
 
 void DisplayEventDispatcher::dispose() {
     ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this);
 
-    if (!mReceiver.initCheck()) {
+    if (!mReceiver.initCheck() && mLooper != nullptr) {
         mLooper->removeFd(mReceiver.getFd());
     }
 }
@@ -86,6 +92,22 @@
     return OK;
 }
 
+void DisplayEventDispatcher::toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag) {
+    if (mConfigChangeFlag == configChangeFlag) {
+        return;
+    }
+    status_t status = mReceiver.toggleConfigEvents(configChangeFlag);
+    if (status) {
+        ALOGW("Failed enable config events, status=%d", status);
+        return;
+    }
+    mConfigChangeFlag = configChangeFlag;
+}
+
+int DisplayEventDispatcher::getFd() {
+    return mReceiver.getFd();
+}
+
 int DisplayEventDispatcher::handleEvent(int, int events, void*) {
     if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
         ALOGE("Display event receiver pipe was closed or an error occurred.  "
@@ -140,7 +162,7 @@
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
                     dispatchConfigChanged(ev.header.timestamp, ev.header.displayId,
-                                          ev.config.configId);
+                                          ev.config.configId, ev.config.vsyncPeriod);
                     break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index b8faa2d..fd6aaf8 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -79,6 +79,14 @@
     return NO_INIT;
 }
 
+status_t DisplayEventReceiver::toggleConfigEvents(
+        ISurfaceComposer::ConfigChanged configChangeFlag) {
+    if (mEventConnection != nullptr) {
+        mEventConnection->toggleConfigEvents(configChangeFlag);
+        return NO_ERROR;
+    }
+    return NO_INIT;
+}
 
 ssize_t DisplayEventReceiver::getEvents(DisplayEventReceiver::Event* events,
         size_t count) {
diff --git a/libs/gui/IDisplayEventConnection.cpp b/libs/gui/IDisplayEventConnection.cpp
index c0e246f..dda5acf 100644
--- a/libs/gui/IDisplayEventConnection.cpp
+++ b/libs/gui/IDisplayEventConnection.cpp
@@ -26,7 +26,8 @@
     STEAL_RECEIVE_CHANNEL = IBinder::FIRST_CALL_TRANSACTION,
     SET_VSYNC_RATE,
     REQUEST_NEXT_VSYNC,
-    LAST = REQUEST_NEXT_VSYNC,
+    TOGGLE_CONFIG_EVENTS,
+    LAST = TOGGLE_CONFIG_EVENTS,
 };
 
 } // Anonymous namespace
@@ -53,6 +54,12 @@
         callRemoteAsync<decltype(&IDisplayEventConnection::requestNextVsync)>(
                 Tag::REQUEST_NEXT_VSYNC);
     }
+
+    void toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag) override {
+        callRemoteAsync<decltype(
+                &IDisplayEventConnection::toggleConfigEvents)>(Tag::TOGGLE_CONFIG_EVENTS,
+                                                               configChangeFlag);
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this translation unit (see
@@ -74,6 +81,8 @@
             return callLocal(data, reply, &IDisplayEventConnection::setVsyncRate);
         case Tag::REQUEST_NEXT_VSYNC:
             return callLocalAsync(data, reply, &IDisplayEventConnection::requestNextVsync);
+        case Tag::TOGGLE_CONFIG_EVENTS:
+            return callLocalAsync(data, reply, &IDisplayEventConnection::toggleConfigEvents);
     }
 }
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index f0b7ff5..679d572 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -31,6 +31,9 @@
     status_t initialize();
     void dispose();
     status_t scheduleVsync();
+    void toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag);
+    int getFd();
+    virtual int handleEvent(int receiveFd, int events, void* data);
 
 protected:
     virtual ~DisplayEventDispatcher() = default;
@@ -39,14 +42,14 @@
     sp<Looper> mLooper;
     DisplayEventReceiver mReceiver;
     bool mWaitingForVsync;
+    ISurfaceComposer::ConfigChanged mConfigChangeFlag;
 
     virtual void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) = 0;
     virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
                                  bool connected) = 0;
     virtual void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                       int32_t configId) = 0;
+                                       int32_t configId, nsecs_t vsyncPeriod) = 0;
 
-    virtual int handleEvent(int receiveFd, int events, void* data);
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount);
 };
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index a558cf9..109e28b 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -73,6 +73,7 @@
 
         struct Config {
             int32_t configId;
+            nsecs_t vsyncPeriod;
         };
 
         Header header;
@@ -144,6 +145,11 @@
      */
     status_t requestNextVsync();
 
+    /*
+     * toggleConfigEvents() toggles delivery of config change events.
+     */
+    status_t toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag);
+
 private:
     sp<IDisplayEventConnection> mEventConnection;
     std::unique_ptr<gui::BitTube> mDataChannel;
diff --git a/libs/gui/include/gui/IDisplayEventConnection.h b/libs/gui/include/gui/IDisplayEventConnection.h
index d783f74..8b35ef6 100644
--- a/libs/gui/include/gui/IDisplayEventConnection.h
+++ b/libs/gui/include/gui/IDisplayEventConnection.h
@@ -18,7 +18,7 @@
 
 #include <binder/IInterface.h>
 #include <binder/SafeInterface.h>
-
+#include <gui/ISurfaceComposer.h>
 #include <utils/Errors.h>
 
 #include <cstdint>
@@ -51,6 +51,13 @@
      * requestNextVsync() schedules the next vsync event. It has no effect if the vsync rate is > 0.
      */
     virtual void requestNextVsync() = 0; // Asynchronous
+
+    /*
+     * togglesConfigEvents() configures whether or not display config changes
+     * should be propagated.
+     */
+    virtual void toggleConfigEvents(
+            ISurfaceComposer::ConfigChanged configChangeFlag) = 0; // Asynchronous
 };
 
 class BnDisplayEventConnection : public SafeBnInterface<IDisplayEventConnection> {
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 05ff93e..15d937e 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -17,11 +17,7 @@
 #define LOG_TAG "Choreographer"
 //#define LOG_NDEBUG 0
 
-#include <cinttypes>
-#include <queue>
-#include <thread>
-
-#include <android/choreographer.h>
+#include <apex/choreographer.h>
 #include <gui/DisplayEventDispatcher.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
@@ -29,6 +25,11 @@
 #include <utils/Mutex.h>
 #include <utils/Timers.h>
 
+#include <cinttypes>
+#include <optional>
+#include <queue>
+#include <thread>
+
 namespace android {
 
 static inline const char* toString(bool value) {
@@ -48,11 +49,18 @@
     }
 };
 
+struct RefreshRateCallback {
+    AChoreographer_refreshRateCallback callback;
+    void* data;
+};
 
 class Choreographer : public DisplayEventDispatcher, public MessageHandler {
 public:
+    explicit Choreographer(const sp<Looper>& looper);
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                   AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay);
+    void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
+    void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb);
 
     enum {
         MSG_SCHEDULE_CALLBACKS = 0,
@@ -61,28 +69,30 @@
     virtual void handleMessage(const Message& message) override;
 
     static Choreographer* getForThread();
-
-protected:
     virtual ~Choreographer() = default;
 
 private:
-    explicit Choreographer(const sp<Looper>& looper);
     Choreographer(const Choreographer&) = delete;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
-    void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
-                               int32_t configId) override;
+    void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t configId,
+                               nsecs_t vsyncPeriod) override;
 
     void scheduleCallbacks();
 
     // Protected by mLock
-    std::priority_queue<FrameCallback> mCallbacks;
+    std::priority_queue<FrameCallback> mFrameCallbacks;
+
+    // Protected by mLock
+    std::vector<RefreshRateCallback> mRefreshRateCallbacks;
+    nsecs_t mVsyncPeriod = 0;
 
     mutable Mutex mLock;
 
     const sp<Looper> mLooper;
     const std::thread::id mThreadId;
+    const std::optional<PhysicalDisplayId> mInternalDisplayId;
 };
 
 
@@ -104,9 +114,11 @@
     return gChoreographer;
 }
 
-Choreographer::Choreographer(const sp<Looper>& looper) :
-    DisplayEventDispatcher(looper), mLooper(looper), mThreadId(std::this_thread::get_id()) {
-}
+Choreographer::Choreographer(const sp<Looper>& looper)
+      : DisplayEventDispatcher(looper),
+        mLooper(looper),
+        mThreadId(std::this_thread::get_id()),
+        mInternalDisplayId(SurfaceComposerClient::getInternalDisplayId()) {}
 
 void Choreographer::postFrameCallbackDelayed(
         AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay) {
@@ -114,25 +126,55 @@
     FrameCallback callback{cb, cb64, data, now + delay};
     {
         AutoMutex _l{mLock};
-        mCallbacks.push(callback);
+        mFrameCallbacks.push(callback);
     }
     if (callback.dueTime <= now) {
         if (std::this_thread::get_id() != mThreadId) {
-            Message m{MSG_SCHEDULE_VSYNC};
-            mLooper->sendMessage(this, m);
+            if (mLooper != nullptr) {
+                Message m{MSG_SCHEDULE_VSYNC};
+                mLooper->sendMessage(this, m);
+            } else {
+                scheduleVsync();
+            }
         } else {
             scheduleVsync();
         }
     } else {
-        Message m{MSG_SCHEDULE_CALLBACKS};
-        mLooper->sendMessageDelayed(delay, this, m);
+        if (mLooper != nullptr) {
+            Message m{MSG_SCHEDULE_CALLBACKS};
+            mLooper->sendMessageDelayed(delay, this, m);
+        } else {
+            scheduleCallbacks();
+        }
+    }
+}
+
+void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) {
+    {
+        AutoMutex _l{mLock};
+        mRefreshRateCallbacks.emplace_back(RefreshRateCallback{cb, data});
+        toggleConfigEvents(ISurfaceComposer::ConfigChanged::eConfigChangedDispatch);
+    }
+}
+
+void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb) {
+    {
+        AutoMutex _l{mLock};
+        mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(),
+                                                   mRefreshRateCallbacks.end(),
+                                                   [&](const RefreshRateCallback& callback) {
+                                                       return cb == callback.callback;
+                                                   }));
+        if (mRefreshRateCallbacks.empty()) {
+            toggleConfigEvents(ISurfaceComposer::ConfigChanged::eConfigChangedSuppress);
+        }
     }
 }
 
 void Choreographer::scheduleCallbacks() {
     AutoMutex _{mLock};
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    if (mCallbacks.top().dueTime <= now) {
+    if (mFrameCallbacks.top().dueTime <= now) {
         ALOGV("choreographer %p ~ scheduling vsync", this);
         scheduleVsync();
         return;
@@ -147,9 +189,9 @@
     {
         AutoMutex _l{mLock};
         nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-        while (!mCallbacks.empty() && mCallbacks.top().dueTime < now) {
-            callbacks.push_back(mCallbacks.top());
-            mCallbacks.pop();
+        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
+            callbacks.push_back(mFrameCallbacks.top());
+            mFrameCallbacks.pop();
         }
     }
     for (const auto& cb : callbacks) {
@@ -167,11 +209,25 @@
             this, displayId, toString(connected));
 }
 
-void Choreographer::dispatchConfigChanged(nsecs_t, PhysicalDisplayId displayId,
-                                          int32_t configId) {
-    ALOGV("choreographer %p ~ received config changed event (displayId=%"
-            ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", configId=%s), ignoring.",
-            this, displayId, toString(configId));
+// TODO(b/74619554): The PhysicalDisplayId is ignored because currently
+// Choreographer only supports dispatching VSYNC events for the internal
+// display, so as such Choreographer does not support the notion of multiple
+// displays. When multi-display choreographer is properly supported, then
+// PhysicalDisplayId should no longer be ignored.
+void Choreographer::dispatchConfigChanged(nsecs_t, PhysicalDisplayId, int32_t,
+                                          nsecs_t vsyncPeriod) {
+    {
+        AutoMutex _l{mLock};
+        for (const auto& cb : mRefreshRateCallbacks) {
+            // Only perform the callback when the old refresh rate is different
+            // from the new refresh rate, so that we don't dispatch the callback
+            // on every single configuration change.
+            if (mVsyncPeriod != vsyncPeriod) {
+                cb.callback(vsyncPeriod, cb.data);
+                mVsyncPeriod = vsyncPeriod;
+            }
+        }
+    }
 }
 
 void Choreographer::handleMessage(const Message& message) {
@@ -189,7 +245,7 @@
 
 /* Glue for the NDK interface */
 
-using android::Choreographer;
+using namespace android;
 
 static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) {
     return reinterpret_cast<Choreographer*>(choreographer);
@@ -223,3 +279,41 @@
     AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
             nullptr, callback, data, ms2ns(delayMillis));
 }
+void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
+                                                AChoreographer_refreshRateCallback callback,
+                                                void* data) {
+    AChoreographer_to_Choreographer(choreographer)->registerRefreshRateCallback(callback, data);
+}
+void AChoreographer_unregisterRefreshRateCallback(AChoreographer* choreographer,
+                                                  AChoreographer_refreshRateCallback callback) {
+    AChoreographer_to_Choreographer(choreographer)->unregisterRefreshRateCallback(callback);
+}
+
+AChoreographer* AChoreographer_create() {
+    Choreographer* choreographer = new Choreographer(nullptr);
+    status_t result = choreographer->initialize();
+    if (result != OK) {
+        ALOGW("Failed to initialize");
+        return nullptr;
+    }
+    return Choreographer_to_AChoreographer(choreographer);
+}
+
+void AChoreographer_destroy(AChoreographer* choreographer) {
+    if (choreographer == nullptr) {
+        return;
+    }
+
+    delete AChoreographer_to_Choreographer(choreographer);
+}
+
+int AChoreographer_getFd(AChoreographer* choreographer) {
+    return AChoreographer_to_Choreographer(choreographer)->getFd();
+}
+
+void AChoreographer_handlePendingEvents(AChoreographer* choreographer, void* data) {
+    // Pass dummy fd and events args to handleEvent, since the underlying
+    // DisplayEventDispatcher doesn't need them outside of validating that a
+    // Looper instance didn't break, but these args circumvent those checks.
+    AChoreographer_to_Choreographer(choreographer)->handleEvent(-1, Looper::EVENT_INPUT, data);
+}
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 7a497ea..d37b9a1 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 ndk_headers {
-    name: "libachoreographer_ndk_headers",
+    name: "libnativedisplay_ndk_headers",
     from: "include/android",
     to: "android",
     srcs: ["include/android/*.h"],
diff --git a/libs/nativedisplay/include/apex/choreographer.h b/libs/nativedisplay/include/apex/choreographer.h
new file mode 100644
index 0000000..5251fd3
--- /dev/null
+++ b/libs/nativedisplay/include/apex/choreographer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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 <android/choreographer.h>
+#include <inttypes.h>
+
+__BEGIN_DECLS
+
+/**
+ * Prototype of the function that is called when the display refresh rate
+ * changes. It's passed the new vsync period in nanoseconds, as well as the data
+ * pointer provided by the application that registered a callback.
+ */
+typedef void (*AChoreographer_refreshRateCallback)(int64_t vsyncPeriodNanos, void* data);
+
+/**
+ * Registers a callback to be run when the display refresh rate changes.
+ */
+void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
+                                                AChoreographer_refreshRateCallback, void* data);
+
+/**
+ * Unregisters a callback to be run when the display refresh rate changes.
+ */
+void AChoreographer_unregisterRefreshRateCallback(AChoreographer* choreographer,
+                                                  AChoreographer_refreshRateCallback);
+
+/**
+ * Creates an instance of AChoreographer.
+ *
+ * The key differences between this method and AChoreographer_getInstance are:
+ * 1. The returned AChoreographer instance is not a thread-local, and
+ * 2. This method does not require an existing ALooper attached to the thread.
+ */
+AChoreographer* AChoreographer_create();
+
+/**
+ * Destroys a choreographer instance created from AChoreographer_create.
+ */
+void AChoreographer_destroy(AChoreographer* choreographer);
+
+/**
+ * Returns the underlying file descriptor associated with this choreographer
+ * instance.
+ *
+ * The caller can listen to the file descriptor to respond to any AChoreographer
+ * events. One such way is registering the file descriptor to a Looper instance,
+ * although this is not a requirement.
+ */
+int AChoreographer_getFd(AChoreographer* choreographer);
+
+/**
+ * Provides a callback to handle all pending events emitted by this
+ * choreographer instance. Specifically, this delegates to the callbacks
+ * previously registered to choreographer.
+ *
+ * If the associated file descriptor is attached to a Looper instance, then the
+ * callback attached to that Looper is expected to handle exceptional Looper
+ * events.
+ */
+void AChoreographer_handlePendingEvents(AChoreographer* choreographer, void* data);
+
+__END_DECLS
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index 3b29c18..3b6a241 100644
--- a/libs/nativedisplay/libnativedisplay.map.txt
+++ b/libs/nativedisplay/libnativedisplay.map.txt
@@ -5,6 +5,8 @@
     AChoreographer_postFrameCallbackDelayed; # apex # introduced=30
     AChoreographer_postFrameCallback64; # apex # introduced=30
     AChoreographer_postFrameCallbackDelayed64; # apex # introduced=30
+    AChoreographer_registerRefreshRateCallback; # apex # introduced=30
+    AChoreographer_unregisterRefreshRateCallback; # apex # introduced=30
   local:
     *;
 };
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 0ba01f4..842af18 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -138,6 +138,11 @@
     static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
     static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
     static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+    static_assert(static_cast<int>(ADATASPACE_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_ADOBE_RGB));
+    static_assert(static_cast<int>(ADATASPACE_BT2020) == static_cast<int>(HAL_DATASPACE_BT2020));
+    static_assert(static_cast<int>(ADATASPACE_BT709) == static_cast<int>(HAL_DATASPACE_V0_BT709));
+    static_assert(static_cast<int>(ADATASPACE_DCI_P3) == static_cast<int>(HAL_DATASPACE_DCI_P3));
+    static_assert(static_cast<int>(ADATASPACE_SRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SRGB_LINEAR));
 
     if (!window || !query(window, NATIVE_WINDOW_IS_VALID) ||
             !isDataSpaceValid(window, dataSpace)) {
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index 2899bcf..e759513 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -101,6 +101,56 @@
      * Use full range, SMPTE 2084 (PQ) transfer and BT2020 standard
      */
     ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
+
+    /**
+     * Adobe RGB
+     *
+     * Use full range, gamma 2.2 transfer and Adobe RGB primaries
+     * Note: Application is responsible for gamma encoding the data as
+     * a 2.2 gamma encoding is not supported in HW.
+     */
+    ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL
+
+    /**
+     * ITU-R Recommendation 2020 (BT.2020)
+     *
+     * Ultra High-definition television
+     *
+     * Use full range, BT.709 transfer and BT2020 standard
+     */
+    ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL
+
+    /**
+     * ITU-R Recommendation 709 (BT.709)
+     *
+     * High-definition television
+     *
+     * Use limited range, BT.709 transfer and BT.709 standard.
+     */
+    ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+
+    /**
+     * SMPTE EG 432-1 and SMPTE RP 431-2.
+     *
+     * Digital Cinema DCI-P3
+     *
+     * Use full range, gamma 2.6 transfer and D65 DCI-P3 standard
+     * Note: Application is responsible for gamma encoding the data as
+     * a 2.6 gamma encoding is not supported in HW.
+     */
+    ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL
+
+    /**
+     * sRGB linear encoding:
+     *
+     * The red, green, and blue components are stored in sRGB space, but
+     * are linear, not gamma-encoded.
+     * The RGB primaries and the white point are the same as BT.709.
+     *
+     * The values are encoded using the full range ([0,255] for 8-bit) for all
+     * components.
+     */
+    ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
 };
 
 __END_DECLS
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 124bda2..043f2ca 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -82,6 +82,7 @@
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.allocator@4.0",
+        "android.hardware.graphics.common-ndk_platform",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@2.1",
@@ -94,14 +95,13 @@
         "libsync",
         "libutils",
         "liblog",
-        "vintf-graphics-common-ndk_platform",
     ],
 
     export_shared_lib_headers: [
         "android.hardware.graphics.common@1.2",
+        "android.hardware.graphics.common-ndk_platform",
         "android.hardware.graphics.mapper@4.0",
         "libgralloctypes",
-        "vintf-graphics-common-ndk_platform",
     ],
 
     static_libs: [
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 0d3c821..246e735 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -109,8 +109,14 @@
         uint32_t consumeSeq;
         InputEvent* event;
 
+        std::chrono::time_point start = std::chrono::steady_clock::now();
         status_t result = WOULD_BLOCK;
         while (result == WOULD_BLOCK) {
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+            if (elapsed > 10ms) {
+                ALOGE("Waited too long for consumer to produce an event, giving up");
+                break;
+            }
             result = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
                                         &event);
         }
@@ -282,12 +288,9 @@
 
     dispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
 
-    MotionEvent event = generateMotionEvent();
-
     for (auto _ : state) {
+        MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        event.setAction(AMOTION_EVENT_ACTION_DOWN);
-        event.setDownTime(now());
         dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
                                      INPUT_EVENT_INJECTION_SYNC_NONE, INJECT_EVENT_TIMEOUT,
                                      POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 4766bce..a3cb4f8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -252,6 +252,10 @@
     return first->getToken() == second->getToken();
 }
 
+static bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
+    return currentTime - entry.eventTime >= STALE_EVENT_TIMEOUT;
+}
+
 // --- InputDispatcherThread ---
 
 class InputDispatcher::InputDispatcherThread : public Thread {
@@ -743,10 +747,6 @@
 #endif
 }
 
-bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
-    return currentTime - entry.eventTime >= STALE_EVENT_TIMEOUT;
-}
-
 bool InputDispatcher::haveCommandsLocked() const {
     return !mCommandQueue.empty();
 }
@@ -1111,7 +1111,7 @@
         sp<Connection> connection =
                 getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
         if (connection != nullptr) {
-            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
+            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
         } else {
             if (DEBUG_FOCUS) {
                 ALOGD("Dropping event delivery to target with channel '%s' because it "
@@ -2045,7 +2045,7 @@
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                  const sp<Connection>& connection,
                                                  EventEntry* eventEntry,
-                                                 const InputTarget* inputTarget) {
+                                                 const InputTarget& inputTarget) {
     if (ATRACE_ENABLED()) {
         std::string message =
                 StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, sequenceNum=%" PRIu32 ")",
@@ -2056,9 +2056,9 @@
     ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
           "xOffset=%f, yOffset=%f, globalScaleFactor=%f, "
           "windowScaleFactor=(%f, %f), pointerIds=0x%x",
-          connection->getInputChannelName().c_str(), inputTarget->flags, inputTarget->xOffset,
-          inputTarget->yOffset, inputTarget->globalScaleFactor, inputTarget->windowXScale,
-          inputTarget->windowYScale, inputTarget->pointerIds.value);
+          connection->getInputChannelName().c_str(), inputTarget.flags, inputTarget.xOffset,
+          inputTarget.yOffset, inputTarget.globalScaleFactor, inputTarget.windowXScale,
+          inputTarget.windowYScale, inputTarget.pointerIds.value);
 #endif
 
     // Skip this event if the connection status is not normal.
@@ -2072,13 +2072,15 @@
     }
 
     // Split a motion event if needed.
-    if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
-        ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
+    if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
+        LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
+                            "Entry type %s should not have FLAG_SPLIT",
+                            EventEntry::typeToString(eventEntry->type));
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
-        if (inputTarget->pointerIds.count() != originalMotionEntry.pointerCount) {
+        if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
             MotionEntry* splitMotionEntry =
-                    splitMotionEvent(originalMotionEntry, inputTarget->pointerIds);
+                    splitMotionEvent(originalMotionEntry, inputTarget.pointerIds);
             if (!splitMotionEntry) {
                 return; // split event was dropped
             }
@@ -2100,7 +2102,7 @@
 void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                    const sp<Connection>& connection,
                                                    EventEntry* eventEntry,
-                                                   const InputTarget* inputTarget) {
+                                                   const InputTarget& inputTarget) {
     if (ATRACE_ENABLED()) {
         std::string message =
                 StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, sequenceNum=%" PRIu32
@@ -2133,7 +2135,7 @@
 
 void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
                                                  EventEntry* eventEntry,
-                                                 const InputTarget* inputTarget,
+                                                 const InputTarget& inputTarget,
                                                  int32_t dispatchMode) {
     if (ATRACE_ENABLED()) {
         std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)",
@@ -2141,7 +2143,7 @@
                                            dispatchModeToString(dispatchMode).c_str());
         ATRACE_NAME(message.c_str());
     }
-    int32_t inputTargetFlags = inputTarget->flags;
+    int32_t inputTargetFlags = inputTarget.flags;
     if (!(inputTargetFlags & dispatchMode)) {
         return;
     }
@@ -2149,11 +2151,11 @@
 
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
-    DispatchEntry* dispatchEntry =
-            new DispatchEntry(eventEntry, // increments ref
-                              inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
-                              inputTarget->globalScaleFactor, inputTarget->windowXScale,
-                              inputTarget->windowYScale);
+    std::unique_ptr<DispatchEntry> dispatchEntry =
+            std::make_unique<DispatchEntry>(eventEntry, // increments ref
+                                            inputTargetFlags, inputTarget.xOffset,
+                                            inputTarget.yOffset, inputTarget.globalScaleFactor,
+                                            inputTarget.windowXScale, inputTarget.windowYScale);
 
     // Apply target flags and update the connection's input state.
     switch (eventEntry->type) {
@@ -2168,7 +2170,6 @@
                 ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
                       connection->getInputChannelName().c_str());
 #endif
-                delete dispatchEntry;
                 return; // skip the inconsistent event
             }
             break;
@@ -2215,12 +2216,11 @@
                       "event",
                       connection->getInputChannelName().c_str());
 #endif
-                delete dispatchEntry;
                 return; // skip the inconsistent event
             }
 
             dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
-                                            inputTarget->inputChannel->getConnectionToken());
+                                            inputTarget.inputChannel->getConnectionToken());
 
             break;
         }
@@ -2238,7 +2238,7 @@
     }
 
     // Enqueue the dispatch entry.
-    connection->outboundQueue.push_back(dispatchEntry);
+    connection->outboundQueue.push_back(dispatchEntry.release());
     traceOutboundQueueLength(connection);
 }
 
@@ -2625,7 +2625,7 @@
             target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
 
             enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
-                                       &target, InputTarget::FLAG_DISPATCH_AS_IS);
+                                       target, InputTarget::FLAG_DISPATCH_AS_IS);
 
             cancelationEventEntry->release();
         }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 96a09e3..50b5250 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -168,9 +168,6 @@
     bool isAppSwitchPendingLocked() REQUIRES(mLock);
     void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock);
 
-    // Stale event latency optimization.
-    static bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry);
-
     // Blocked event latency optimization.  Drops old events when the user intends
     // to transfer focus to a new application.
     EventEntry* mNextUnblockedEvent GUARDED_BY(mLock);
@@ -381,13 +378,13 @@
     // with the mutex held makes it easier to ensure that connection invariants are maintained.
     // If needed, the methods post commands to run later once the critical bits are done.
     void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
-                                    EventEntry* eventEntry, const InputTarget* inputTarget)
+                                    EventEntry* eventEntry, const InputTarget& inputTarget)
             REQUIRES(mLock);
     void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
-                                      EventEntry* eventEntry, const InputTarget* inputTarget)
+                                      EventEntry* eventEntry, const InputTarget& inputTarget)
             REQUIRES(mLock);
     void enqueueDispatchEntryLocked(const sp<Connection>& connection, EventEntry* eventEntry,
-                                    const InputTarget* inputTarget, int32_t dispatchMode)
+                                    const InputTarget& inputTarget, int32_t dispatchMode)
             REQUIRES(mLock);
     void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
             REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 2e9bca2..1ba5eff 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -108,7 +108,7 @@
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
-    BitSet32 pointerIds{};
+    BitSet32 pointerIds;
 };
 
 std::string dispatchModeToString(int32_t dispatchMode);
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 945abd7..e4e4bc7 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -455,6 +455,10 @@
         mLastFrameNumberReceived = item.mFrameNumber;
         mQueueItemCondition.broadcast();
     }
+
+    const int32_t layerId = getSequence();
+    mFlinger->mFrameTracer->traceTimestamp(layerId, item.mGraphicBuffer->getId(), item.mFrameNumber,
+                                           systemTime(), FrameTracer::FrameEvent::QUEUE);
     mConsumer->onBufferAvailable(item);
 }
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index ff800c3..5bdef58 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -103,10 +103,11 @@
 }
 
 DisplayEventReceiver::Event makeConfigChanged(PhysicalDisplayId displayId,
-                                              HwcConfigIndexType configId) {
+                                              HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
     DisplayEventReceiver::Event event;
     event.header = {DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED, displayId, systemTime()};
     event.config.configId = configId.value();
+    event.config.vsyncPeriod = vsyncPeriod;
     return event;
 }
 
@@ -116,7 +117,7 @@
                                              ResyncCallback resyncCallback,
                                              ISurfaceComposer::ConfigChanged configChanged)
       : resyncCallback(std::move(resyncCallback)),
-        configChanged(configChanged),
+        mConfigChanged(configChanged),
         mEventThread(eventThread),
         mChannel(gui::BitTube::DefaultSize) {}
 
@@ -145,6 +146,18 @@
     mEventThread->requestNextVsync(this);
 }
 
+void EventThreadConnection::toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag) {
+    ATRACE_NAME("enableConfigEvents");
+    mConfigChanged = configChangeFlag;
+
+    // In principle it's possible for rapidly toggling config events to drop an
+    // event here, but it's unlikely in practice.
+    if (configChangeFlag == ISurfaceComposer::eConfigChangedDispatch) {
+        mForcedConfigChangeDispatch = true;
+        mEventThread->requestLatestConfig();
+    }
+}
+
 status_t EventThreadConnection::postEvent(const DisplayEventReceiver::Event& event) {
     ssize_t size = DisplayEventReceiver::sendEvents(&mChannel, &event, 1);
     return size < 0 ? status_t(size) : status_t(NO_ERROR);
@@ -257,6 +270,24 @@
     }
 }
 
+void EventThread::requestLatestConfig() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    auto pendingConfigChange =
+            std::find_if(std::begin(mPendingEvents), std::end(mPendingEvents),
+                         [&](const DisplayEventReceiver::Event& event) {
+                             return event.header.type ==
+                                     DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED;
+                         });
+
+    // If we didn't find a pending config change event, then push out the
+    // latest one we've ever seen.
+    if (pendingConfigChange == std::end(mPendingEvents)) {
+        mPendingEvents.push_back(mLastConfigChangeEvent);
+    }
+
+    mCondition.notify_all();
+}
+
 void EventThread::onScreenReleased() {
     std::lock_guard<std::mutex> lock(mMutex);
     if (!mVSyncState || mVSyncState->synthetic) {
@@ -292,10 +323,11 @@
     mCondition.notify_all();
 }
 
-void EventThread::onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) {
+void EventThread::onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId,
+                                  nsecs_t vsyncPeriod) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    mPendingEvents.push_back(makeConfigChanged(displayId, configId));
+    mPendingEvents.push_back(makeConfigChanged(displayId, configId, vsyncPeriod));
     mCondition.notify_all();
 }
 
@@ -325,6 +357,9 @@
                         mInterceptVSyncsCallback(event->header.timestamp);
                     }
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
+                    mLastConfigChangeEvent = *event;
+                    break;
             }
         }
 
@@ -398,8 +433,11 @@
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
             return true;
 
-        case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
-            return connection->configChanged == ISurfaceComposer::eConfigChangedDispatch;
+        case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED: {
+            const bool forcedDispatch = connection->mForcedConfigChangeDispatch.exchange(false);
+            return forcedDispatch ||
+                    connection->mConfigChanged == ISurfaceComposer::eConfigChangedDispatch;
+        }
 
         case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
             switch (connection->vsyncRequest) {
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index a42546c..641b2a5 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -81,12 +81,19 @@
     status_t stealReceiveChannel(gui::BitTube* outChannel) override;
     status_t setVsyncRate(uint32_t rate) override;
     void requestNextVsync() override; // asynchronous
+    void toggleConfigEvents(ISurfaceComposer::ConfigChanged configChangeFlag) override;
 
     // Called in response to requestNextVsync.
     const ResyncCallback resyncCallback;
 
     VSyncRequest vsyncRequest = VSyncRequest::None;
-    const ISurfaceComposer::ConfigChanged configChanged;
+    std::atomic<ISurfaceComposer::ConfigChanged> mConfigChanged =
+            ISurfaceComposer::ConfigChanged::eConfigChangedSuppress;
+    // Store whether we need to force dispatching a config change separately -
+    // if mConfigChanged ever changes before the config change is dispatched
+    // then we still need to propagate an initial config to the app if we
+    // haven't already.
+    std::atomic<bool> mForcedConfigChangeDispatch = false;
 
 private:
     virtual void onFirstRef();
@@ -110,7 +117,8 @@
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
     // called when SF changes the active config and apps needs to be notified about the change
-    virtual void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) = 0;
+    virtual void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId,
+                                 nsecs_t vsyncPeriod) = 0;
 
     virtual void dump(std::string& result) const = 0;
 
@@ -121,6 +129,11 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
+
+    // Dispatches the most recent configuration
+    // Usage of this method assumes that only the primary internal display
+    // supports multiple display configurations.
+    virtual void requestLatestConfig() = 0;
 };
 
 namespace impl {
@@ -138,6 +151,7 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
+    void requestLatestConfig() override;
 
     // called before the screen is turned off from main thread
     void onScreenReleased() override;
@@ -147,7 +161,8 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
-    void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) override;
+    void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId,
+                         nsecs_t vsyncPeriod) override;
 
     void dump(std::string& result) const override;
 
@@ -182,6 +197,7 @@
 
     std::vector<wp<EventThreadConnection>> mDisplayEventConnections GUARDED_BY(mMutex);
     std::deque<DisplayEventReceiver::Event> mPendingEvents GUARDED_BY(mMutex);
+    DisplayEventReceiver::Event mLastConfigChangeEvent GUARDED_BY(mMutex);
 
     // VSYNC state of connected display.
     struct VSyncState {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 847e20c..692ded9 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -126,7 +126,7 @@
         return BAD_VALUE;
     }
     const RefreshRate& refreshRate = mRefreshRates.at(defaultConfigId);
-    if (refreshRate.fps < minRefreshRate || refreshRate.fps > maxRefreshRate) {
+    if (!refreshRate.inPolicy(minRefreshRate, maxRefreshRate)) {
         return BAD_VALUE;
     }
     mDefaultConfig = defaultConfigId;
@@ -180,8 +180,8 @@
           group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
     getSortedRefreshRateList(
             [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
-                return refreshRate.configGroup == group && refreshRate.fps >= mMinRefreshRateFps &&
-                        refreshRate.fps <= mMaxRefreshRateFps;
+                return refreshRate.configGroup == group &&
+                        refreshRate.inPolicy(mMinRefreshRateFps, mMaxRefreshRateFps);
             },
             &mAvailableRefreshRates);
     LOG_ALWAYS_FATAL_IF(mAvailableRefreshRates.empty(),
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 1e740ca..0c3369a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -44,6 +44,9 @@
 class RefreshRateConfigs {
 public:
     struct RefreshRate {
+        // The tolerance within which we consider FPS approximately equals.
+        static constexpr float FPS_EPSILON = 0.001f;
+
         RefreshRate(HwcConfigIndexType configId, nsecs_t vsyncPeriod,
                     HwcConfigGroupType configGroup, std::string name, float fps)
               : configId(configId),
@@ -63,6 +66,12 @@
         // Refresh rate in frames per second
         const float fps = 0;
 
+        // 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));
+        }
+
         bool operator!=(const RefreshRate& other) const {
             return configId != other.configId || vsyncPeriod != other.vsyncPeriod ||
                     configGroup != other.configGroup;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index e845e8c..ff9cf86 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -222,9 +222,9 @@
 }
 
 void Scheduler::onConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
-                                HwcConfigIndexType configId) {
+                                HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
     RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->onConfigChanged(displayId, configId);
+    mConnections[handle].thread->onConfigChanged(displayId, configId, vsyncPeriod);
 }
 
 void Scheduler::dump(ConnectionHandle handle, std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 2fa8b3f..2cdb757 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -74,7 +74,8 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, HwcConfigIndexType configId);
+    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, HwcConfigIndexType configId,
+                         nsecs_t vsyncPeriod);
 
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index c471e49..a652053 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -154,7 +154,7 @@
         mTracker->addVsyncTimestamp(signalTime);
     }
 
-    return false; // TODO(b/144707443): add policy for turning on HWVsync.
+    return mMoreSamplesNeeded;
 }
 
 void VSyncReactor::setIgnorePresentFences(bool ignoration) {
@@ -176,14 +176,9 @@
 }
 
 void VSyncReactor::setPeriod(nsecs_t period) {
-    mTracker->setPeriod(period);
-    {
-        std::lock_guard<std::mutex> lk(mMutex);
-        mPeriodChangeInProgress = true;
-        for (auto& entry : mCallbacks) {
-            entry.second->setPeriod(period);
-        }
-    }
+    std::lock_guard lk(mMutex);
+    mLastHwVsync.reset();
+    mPeriodTransitioningTo = period;
 }
 
 nsecs_t VSyncReactor::getPeriod() {
@@ -194,15 +189,40 @@
 
 void VSyncReactor::endResync() {}
 
+bool VSyncReactor::periodChangeDetected(nsecs_t vsync_timestamp) {
+    if (!mLastHwVsync || !mPeriodTransitioningTo) {
+        return false;
+    }
+    auto const distance = vsync_timestamp - *mLastHwVsync;
+    return std::abs(distance - *mPeriodTransitioningTo) < std::abs(distance - getPeriod());
+}
+
 bool VSyncReactor::addResyncSample(nsecs_t timestamp, bool* periodFlushed) {
     assert(periodFlushed);
-    mTracker->addVsyncTimestamp(timestamp);
-    {
-        std::lock_guard<std::mutex> lk(mMutex);
-        *periodFlushed = mPeriodChangeInProgress;
-        mPeriodChangeInProgress = false;
+
+    std::lock_guard<std::mutex> lk(mMutex);
+    if (periodChangeDetected(timestamp)) {
+        mMoreSamplesNeeded = false;
+        *periodFlushed = true;
+
+        mTracker->setPeriod(*mPeriodTransitioningTo);
+        for (auto& entry : mCallbacks) {
+            entry.second->setPeriod(*mPeriodTransitioningTo);
+        }
+
+        mPeriodTransitioningTo.reset();
+        mLastHwVsync.reset();
+    } else if (mPeriodTransitioningTo) {
+        mLastHwVsync = timestamp;
+        mMoreSamplesNeeded = true;
+        *periodFlushed = false;
+    } else {
+        mMoreSamplesNeeded = false;
+        *periodFlushed = false;
     }
-    return false;
+
+    mTracker->addVsyncTimestamp(timestamp);
+    return mMoreSamplesNeeded;
 }
 
 status_t VSyncReactor::addEventListener(const char* name, nsecs_t phase,
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 29a0a11..73a5e37 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -61,6 +61,8 @@
     void reset() final;
 
 private:
+    bool periodChangeDetected(nsecs_t vsync_timestamp) REQUIRES(mMutex);
+
     std::unique_ptr<Clock> const mClock;
     std::unique_ptr<VSyncTracker> const mTracker;
     std::unique_ptr<VSyncDispatch> const mDispatch;
@@ -69,7 +71,11 @@
     std::mutex mMutex;
     bool mIgnorePresentFences GUARDED_BY(mMutex) = false;
     std::vector<std::shared_ptr<FenceTime>> mUnfiredFences GUARDED_BY(mMutex);
-    bool mPeriodChangeInProgress GUARDED_BY(mMutex) = false;
+
+    bool mMoreSamplesNeeded GUARDED_BY(mMutex) = false;
+    std::optional<nsecs_t> mPeriodTransitioningTo GUARDED_BY(mMutex);
+    std::optional<nsecs_t> mLastHwVsync GUARDED_BY(mMutex);
+
     std::unordered_map<DispSync::Callback*, std::unique_ptr<CallbackRepeater>> mCallbacks
             GUARDED_BY(mMutex);
 };
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d02d783..8c1d168 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -953,8 +953,11 @@
     ATRACE_INT("ActiveConfigFPS", refreshRate.fps);
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
+        const nsecs_t vsyncPeriod =
+                mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId)
+                        .vsyncPeriod;
         mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
-                                    mUpcomingActiveConfig.configId);
+                                    mUpcomingActiveConfig.configId, vsyncPeriod);
     }
 }
 
@@ -2688,6 +2691,17 @@
     mRegionSamplingThread =
             new RegionSamplingThread(*this, *mScheduler,
                                      RegionSamplingThread::EnvironmentTimingTunables());
+    // Dispatch a config change request for the primary display on scheduler
+    // initialization, so that the EventThreads always contain a reference to a
+    // prior configuration.
+    //
+    // This is a bit hacky, but this avoids a back-pointer into the main SF
+    // classes from EventThread, and there should be no run-time binder cost
+    // anyway since there are no connected apps at this point.
+    const nsecs_t vsyncPeriod =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig).vsyncPeriod;
+    mScheduler->onConfigChanged(mAppConnectionHandle, primaryDisplayId.value, currentConfig,
+                                vsyncPeriod);
 }
 
 void SurfaceFlinger::commitTransaction()
@@ -5577,7 +5591,10 @@
 
         auto configId = HwcConfigIndexType(defaultConfig);
         display->setActiveConfig(configId);
-        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, configId);
+        const nsecs_t vsyncPeriod =
+                mRefreshRateConfigs->getRefreshRateFromConfigId(configId).vsyncPeriod;
+        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, configId,
+                                    vsyncPeriod);
         return NO_ERROR;
     }
 
@@ -5601,8 +5618,10 @@
     // TODO(b/140204874): This hack triggers a notification that something has changed, so
     // that listeners that care about a change in allowed configs can get the notification.
     // Giving current ActiveConfig so that most other listeners would just drop the event
+    const nsecs_t vsyncPeriod =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(display->getActiveConfig()).vsyncPeriod;
     mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
-                                display->getActiveConfig());
+                                display->getActiveConfig(), vsyncPeriod);
 
     if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
         auto configId = mScheduler->getPreferredConfigId();
diff --git a/services/surfaceflinger/surfaceflinger.rc b/services/surfaceflinger/surfaceflinger.rc
index aea602b..d3942e8 100644
--- a/services/surfaceflinger/surfaceflinger.rc
+++ b/services/surfaceflinger/surfaceflinger.rc
@@ -2,6 +2,7 @@
     class core animation
     user system
     group graphics drmrpc readproc
+    capabilities SYS_NICE
     onrestart restart zygote
     writepid /dev/stune/foreground/tasks
     socket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index dcfc077..0403237 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -42,6 +42,7 @@
         "libtrace_proto",
     ],
     shared_libs: [
+        "android.hardware.graphics.common-ndk_platform",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.composer@2.1",
         "libandroid",
@@ -52,12 +53,15 @@
         "libgui",
         "liblayers_proto",
         "liblog",
+        "libnativewindow",
         "libprotobuf-cpp-full",
         "libtimestats_proto",
         "libui",
         "libutils",
-        "vintf-graphics-common-ndk_platform",
-    ]
+    ],
+    header_libs: [
+        "libnativewindow_headers",
+    ],
 }
 
 cc_defaults {
diff --git a/services/surfaceflinger/tests/CommonTypes_test.cpp b/services/surfaceflinger/tests/CommonTypes_test.cpp
index ab4af09..25b4615 100644
--- a/services/surfaceflinger/tests/CommonTypes_test.cpp
+++ b/services/surfaceflinger/tests/CommonTypes_test.cpp
@@ -16,6 +16,7 @@
 #include <aidl/android/hardware/graphics/common/BlendMode.h>
 #include <aidl/android/hardware/graphics/common/Dataspace.h>
 
+#include <android/data_space.h>
 #include <android/hardware/graphics/common/1.2/types.h>
 #include <android/hardware/graphics/composer/2.1/IComposerClient.h>
 
@@ -34,6 +35,51 @@
 static_assert(static_cast<uint32_t>(AidlBlendMode::COVERAGE) ==
               static_cast<uint32_t>(HidlBlendMode::COVERAGE));
 
+static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
+              static_cast<uint32_t>(AidlDataspace::UNKNOWN));
+static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
+              static_cast<uint32_t>(AidlDataspace::SCRGB_LINEAR));
+static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) == static_cast<uint32_t>(AidlDataspace::SRGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
+              static_cast<uint32_t>(AidlDataspace::SCRGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
+              static_cast<uint32_t>(AidlDataspace::DISPLAY_P3));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
+              static_cast<uint32_t>(AidlDataspace::BT2020_PQ));
+static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
+              static_cast<uint32_t>(AidlDataspace::ADOBE_RGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
+              static_cast<uint32_t>(AidlDataspace::BT2020));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
+              static_cast<uint32_t>(AidlDataspace::BT709));
+static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
+              static_cast<uint32_t>(AidlDataspace::DCI_P3));
+static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
+              static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR));
+
+static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
+              static_cast<uint32_t>(HidlDataspace::UNKNOWN));
+static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
+static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SRGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
+              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
+static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
+              static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020));
+static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
+              static_cast<uint32_t>(HidlDataspace::V0_BT709));
+static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
+              static_cast<uint32_t>(HidlDataspace::DCI_P3));
+static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
+
 static_assert(static_cast<uint32_t>(AidlDataspace::UNKNOWN) ==
               static_cast<uint32_t>(HidlDataspace::UNKNOWN));
 static_assert(static_cast<uint32_t>(AidlDataspace::ARBITRARY) ==
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 420fb29..d51b9a1 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -31,42 +31,40 @@
     void SetUp() override { mDisplayToken = SurfaceComposerClient::getInternalDisplayToken(); }
 
     sp<IBinder> mDisplayToken;
-    int32_t defaultConfigId;
-    float minRefreshRate;
-    float maxRefreshRate;
 };
 
-TEST_F(RefreshRateRangeTest, simpleSetAndGet) {
-    status_t res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 1, 45, 75);
-    EXPECT_EQ(res, NO_ERROR);
+TEST_F(RefreshRateRangeTest, setAllConfigs) {
+    int32_t initialDefaultConfig;
+    float initialMin;
+    float initialMax;
+    status_t res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken,
+                                                                       &initialDefaultConfig,
+                                                                       &initialMin, &initialMax);
+    ASSERT_EQ(res, NO_ERROR);
 
-    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
-                                                              &minRefreshRate, &maxRefreshRate);
-    EXPECT_EQ(res, NO_ERROR);
-    EXPECT_EQ(defaultConfigId, 1);
-    EXPECT_EQ(minRefreshRate, 45);
-    EXPECT_EQ(maxRefreshRate, 75);
+    Vector<DisplayInfo> configs;
+    res = SurfaceComposerClient::getDisplayConfigs(mDisplayToken, &configs);
+    ASSERT_EQ(res, NO_ERROR);
+
+    for (size_t i = 0; i < configs.size(); i++) {
+        res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, i, configs[i].fps,
+                                                                  configs[i].fps);
+        ASSERT_EQ(res, NO_ERROR);
+
+        int defaultConfig;
+        float minFps;
+        float maxFps;
+        res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfig,
+                                                                  &minFps, &maxFps);
+        ASSERT_EQ(res, NO_ERROR);
+        ASSERT_EQ(defaultConfig, i);
+        ASSERT_EQ(minFps, configs[i].fps);
+        ASSERT_EQ(maxFps, configs[i].fps);
+    }
+
+    res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, initialDefaultConfig,
+                                                              initialMin, initialMax);
+    ASSERT_EQ(res, NO_ERROR);
 }
 
-TEST_F(RefreshRateRangeTest, complexSetAndGet) {
-    status_t res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 1, 45, 75);
-    EXPECT_EQ(res, NO_ERROR);
-
-    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
-                                                              &minRefreshRate, &maxRefreshRate);
-    EXPECT_EQ(res, NO_ERROR);
-    EXPECT_EQ(defaultConfigId, 1);
-    EXPECT_EQ(minRefreshRate, 45);
-    EXPECT_EQ(maxRefreshRate, 75);
-
-    // Second call overrides the first one.
-    res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 10, 145, 875);
-    EXPECT_EQ(res, NO_ERROR);
-    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
-                                                              &minRefreshRate, &maxRefreshRate);
-    EXPECT_EQ(res, NO_ERROR);
-    EXPECT_EQ(defaultConfigId, 10);
-    EXPECT_EQ(minRefreshRate, 145);
-    EXPECT_EQ(maxRefreshRate, 875);
-}
 } // namespace android
diff --git a/services/surfaceflinger/tests/SurfaceFlinger_test.filter b/services/surfaceflinger/tests/SurfaceFlinger_test.filter
index 7786638..2bedd7d 100644
--- a/services/surfaceflinger/tests/SurfaceFlinger_test.filter
+++ b/services/surfaceflinger/tests/SurfaceFlinger_test.filter
@@ -1,5 +1,5 @@
 {
         "presubmit": {
-            "filter": "*:-RefreshRateRangeTest.*:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadius/2:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadius/3:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadiusChildCrop/2:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadiusChildCrop/3"
+            "filter": "*:-LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadius/2:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadius/3:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadiusChildCrop/2:LayerTypeAndRenderTypeTransactionTests/LayerTypeAndRenderTypeTransactionTest.SetCornerRadiusChildCrop/3"
         }
 }
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 80bca02..61d4f47 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -85,7 +85,8 @@
     void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                 bool expectedConnected);
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
-                                                      int32_t expectedConfigId);
+                                                      int32_t expectedConfigId,
+                                                      nsecs_t expectedVsyncPeriod);
 
     AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
     AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
@@ -209,13 +210,15 @@
 }
 
 void EventThreadTest::expectConfigChangedEventReceivedByConnection(
-        PhysicalDisplayId expectedDisplayId, int32_t expectedConfigId) {
+        PhysicalDisplayId expectedDisplayId, int32_t expectedConfigId,
+        nsecs_t expectedVsyncPeriod) {
     auto args = mConnectionEventCallRecorder.waitForCall();
     ASSERT_TRUE(args.has_value());
     const auto& event = std::get<0>(args.value());
     EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED, event.header.type);
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
     EXPECT_EQ(expectedConfigId, event.config.configId);
+    EXPECT_EQ(expectedVsyncPeriod, event.config.vsyncPeriod);
 }
 
 namespace {
@@ -450,18 +453,18 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(7));
-    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(7), 16666666);
+    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, 16666666);
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
-    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, HwcConfigIndexType(5));
-    expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5);
+    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, HwcConfigIndexType(5), 16666666);
+    expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, 16666666);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
-    mThread->onConfigChanged(DISPLAY_ID_64BIT, HwcConfigIndexType(7));
-    expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7);
+    mThread->onConfigChanged(DISPLAY_ID_64BIT, HwcConfigIndexType(7), 16666666);
+    expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, 16666666);
 }
 
 TEST_F(EventThreadTest, suppressConfigChanged) {
@@ -470,8 +473,8 @@
             createConnection(suppressConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
 
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(9));
-    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(9), 16666666);
+    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, 16666666);
 
     auto args = suppressConnectionEventRecorder.waitForCall();
     ASSERT_FALSE(args.has_value());
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index ed620ef..cc3c985 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -39,6 +39,7 @@
     static inline const HwcConfigGroupType HWC_GROUP_ID_0 = HwcConfigGroupType(0);
     static inline const HwcConfigGroupType HWC_GROUP_ID_1 = HwcConfigGroupType(1);
     static constexpr int64_t VSYNC_60 = 16666667;
+    static constexpr int64_t VSYNC_60_POINT_4 = 16666665;
     static constexpr int64_t VSYNC_90 = 11111111;
 
     RefreshRateConfigsTest();
@@ -238,6 +239,16 @@
     ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
 }
 
+TEST_F(RefreshRateConfigsTest, testInPolicy) {
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60_POINT_4, HWC_GROUP_ID_0,
+                                         "60fps", 60};
+    ASSERT_TRUE(expectedDefaultConfig.inPolicy(60.000004, 60.000004));
+    ASSERT_TRUE(expectedDefaultConfig.inPolicy(59.0f, 60.1f));
+    ASSERT_FALSE(expectedDefaultConfig.inPolicy(75.0, 90.0));
+    ASSERT_FALSE(expectedDefaultConfig.inPolicy(60.0011, 90.0));
+    ASSERT_FALSE(expectedDefaultConfig.inPolicy(50.0, 59.998));
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 537cc80..72f8bb9 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -159,7 +159,6 @@
     static constexpr nsecs_t mPhase = 3000;
     static constexpr nsecs_t mAnotherPhase = 5200;
     static constexpr nsecs_t period = 10000;
-    static constexpr nsecs_t mAnotherPeriod = 23333;
     static constexpr nsecs_t mFakeCbTime = 2093;
     static constexpr nsecs_t mFakeNow = 2214;
     static constexpr const char mName[] = "callbacky";
@@ -274,10 +273,38 @@
     EXPECT_THAT(mReactor.getPeriod(), Eq(fakePeriod));
 }
 
-TEST_F(VSyncReactorTest, setPeriod) {
-    nsecs_t const fakePeriod = 4098;
-    EXPECT_CALL(*mMockTracker, setPeriod(fakePeriod));
-    mReactor.setPeriod(fakePeriod);
+TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
+    nsecs_t const newPeriod = 5000;
+    EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
+    mReactor.setPeriod(newPeriod);
+
+    bool periodFlushed = true;
+    EXPECT_TRUE(mReactor.addResyncSample(10000, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+
+    EXPECT_TRUE(mReactor.addResyncSample(20000, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+
+    Mock::VerifyAndClearExpectations(mMockTracker.get());
+    EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+
+    EXPECT_FALSE(mReactor.addResyncSample(25000, &periodFlushed));
+    EXPECT_TRUE(periodFlushed);
+}
+
+TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) {
+    nsecs_t const newPeriod = 5000;
+    EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
+    mReactor.setPeriod(newPeriod);
+
+    bool periodFlushed = true;
+    EXPECT_TRUE(mReactor.addResyncSample(5000, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+    Mock::VerifyAndClearExpectations(mMockTracker.get());
+
+    EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+    EXPECT_FALSE(mReactor.addResyncSample(10000, &periodFlushed));
+    EXPECT_TRUE(periodFlushed);
 }
 
 TEST_F(VSyncReactorTest, addResyncSampleTypical) {
@@ -291,12 +318,43 @@
 
 TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) {
     bool periodFlushed = false;
-    nsecs_t const fakeTimestamp = 4398;
-    nsecs_t const newPeriod = 3490;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(fakeTimestamp));
+    nsecs_t const newPeriod = 4000;
+
     mReactor.setPeriod(newPeriod);
-    EXPECT_FALSE(mReactor.addResyncSample(fakeTimestamp, &periodFlushed));
+
+    auto time = 0;
+    auto constexpr numTimestampSubmissions = 10;
+    for (auto i = 0; i < numTimestampSubmissions; i++) {
+        time += period;
+        EXPECT_TRUE(mReactor.addResyncSample(time, &periodFlushed));
+        EXPECT_FALSE(periodFlushed);
+    }
+
+    time += newPeriod;
+    EXPECT_FALSE(mReactor.addResyncSample(time, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
+
+    for (auto i = 0; i < numTimestampSubmissions; i++) {
+        time += newPeriod;
+        EXPECT_FALSE(mReactor.addResyncSample(time, &periodFlushed));
+        EXPECT_FALSE(periodFlushed);
+    }
+}
+
+TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsHwVsync) {
+    auto time = 0;
+    bool periodFlushed = false;
+    nsecs_t const newPeriod = 4000;
+    mReactor.setPeriod(newPeriod);
+
+    time += period;
+    mReactor.addResyncSample(time, &periodFlushed);
+    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+
+    time += newPeriod;
+    mReactor.addResyncSample(time, &periodFlushed);
+
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 static nsecs_t computeWorkload(nsecs_t period, nsecs_t phase) {
@@ -399,20 +457,26 @@
     mReactor.addEventListener(mName, mAnotherPhase, &outerCb, lastCallbackTime);
 }
 
-TEST_F(VSyncReactorTest, changingPeriodChangesOfsetsOnNextCb) {
+TEST_F(VSyncReactorTest, changingPeriodChangesOffsetsOnNextCb) {
+    static constexpr nsecs_t anotherPeriod = 23333;
     Sequence seq;
     EXPECT_CALL(*mMockDispatch, registerCallback(_, std::string(mName)))
             .InSequence(seq)
             .WillOnce(Return(mFakeToken));
     EXPECT_CALL(*mMockDispatch, schedule(mFakeToken, computeWorkload(period, mPhase), mFakeNow))
             .InSequence(seq);
-    EXPECT_CALL(*mMockTracker, setPeriod(mAnotherPeriod));
+    EXPECT_CALL(*mMockTracker, setPeriod(anotherPeriod));
     EXPECT_CALL(*mMockDispatch,
-                schedule(mFakeToken, computeWorkload(mAnotherPeriod, mPhase), mFakeNow))
+                schedule(mFakeToken, computeWorkload(anotherPeriod, mPhase), mFakeNow))
             .InSequence(seq);
 
     mReactor.addEventListener(mName, mPhase, &outerCb, lastCallbackTime);
-    mReactor.setPeriod(mAnotherPeriod);
+
+    bool periodFlushed = false;
+    mReactor.setPeriod(anotherPeriod);
+    EXPECT_TRUE(mReactor.addResyncSample(anotherPeriod, &periodFlushed));
+    EXPECT_FALSE(mReactor.addResyncSample(anotherPeriod * 2, &periodFlushed));
+
     mReactor.addEventListener(mName, mPhase, &outerCb, lastCallbackTime);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index f7c3804..9bda095 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -33,13 +33,14 @@
     MOCK_METHOD0(onScreenReleased, void());
     MOCK_METHOD0(onScreenAcquired, void());
     MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
-    MOCK_METHOD2(onConfigChanged, void(PhysicalDisplayId, HwcConfigIndexType));
+    MOCK_METHOD3(onConfigChanged, void(PhysicalDisplayId, HwcConfigIndexType, nsecs_t));
     MOCK_CONST_METHOD1(dump, void(std::string&));
     MOCK_METHOD1(setPhaseOffset, void(nsecs_t phaseOffset));
     MOCK_METHOD1(registerDisplayEventConnection,
                  status_t(const sp<android::EventThreadConnection> &));
     MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp<android::EventThreadConnection> &));
     MOCK_METHOD1(requestNextVsync, void(const sp<android::EventThreadConnection> &));
+    MOCK_METHOD0(requestLatestConfig, void());
     MOCK_METHOD1(pauseVsyncCallback, void(bool));
 };