Merge "Add a config to allow touches while the display is off."
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index 7e89220..bb3faaf 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
 
 package android.view;
 
-parcelable Surface cpp_header "gui/view/Surface.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 3d81c32..6d1dfe8 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -58,7 +58,7 @@
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.common-V4-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 0021bd6..97e45c6 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -360,11 +360,12 @@
                     }
                 }
                 for (const auto& staleRelease : staleReleases) {
-                    BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback");
-                    BBQ_TRACE("FakeReleaseCallback");
                     releaseBufferCallbackLocked(staleRelease,
-                        stat.previousReleaseFence ? stat.previousReleaseFence : Fence::NO_FENCE,
-                        stat.currentMaxAcquiredBufferCount);
+                                                stat.previousReleaseFence
+                                                        ? stat.previousReleaseFence
+                                                        : Fence::NO_FENCE,
+                                                stat.currentMaxAcquiredBufferCount,
+                                                true /* fakeRelease */);
                 }
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
@@ -408,11 +409,13 @@
     BBQ_TRACE();
 
     std::unique_lock _lock{mMutex};
-    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount);
+    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
+                                false /* fakeRelease */);
 }
 
-void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
-        const sp<Fence>& releaseFence, std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+void BLASTBufferQueue::releaseBufferCallbackLocked(
+        const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+        std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) {
     ATRACE_CALL();
     BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str());
 
@@ -435,6 +438,11 @@
     auto rb = ReleasedBuffer{id, releaseFence};
     if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
         mPendingRelease.emplace_back(rb);
+        if (fakeRelease) {
+            BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64,
+                     id.framenumber);
+            BBQ_TRACE("FakeReleaseCallback");
+        }
     }
 
     // Release all buffers that are beyond the ones that we need to hold
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
index cdc9376..1c61d6b 100644
--- a/libs/gui/fuzzer/Android.bp
+++ b/libs/gui/fuzzer/Android.bp
@@ -46,7 +46,7 @@
         "android.hardware.configstore-utils",
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "android.hidl.token@1.0",
         "libSurfaceFlingerProp",
         "libgui",
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 957652e..47dcc42 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -93,7 +93,8 @@
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                std::optional<uint32_t> currentMaxAcquiredBufferCount);
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
-                               std::optional<uint32_t> currentMaxAcquiredBufferCount);
+                                     std::optional<uint32_t> currentMaxAcquiredBufferCount,
+                                     bool fakeRelease);
     void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 1f19f4e..7aec0bf 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -113,6 +113,24 @@
         return surface != nullptr && surface->getIGraphicBufferProducer() != nullptr;
     }
 
+    static sp<IGraphicBufferProducer> getIGraphicBufferProducer(ANativeWindow* window) {
+        int val;
+        if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+            val == NATIVE_WINDOW_SURFACE) {
+            return ((Surface*) window)->mGraphicBufferProducer;
+        }
+        return nullptr;
+    }
+
+    static sp<IBinder> getSurfaceControlHandle(ANativeWindow* window) {
+        int val;
+        if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+            val == NATIVE_WINDOW_SURFACE) {
+            return ((Surface*) window)->mSurfaceControlHandle;
+        }
+        return nullptr;
+    }
+
     /* Attaches a sideband buffer stream to the Surface's IGraphicBufferProducer.
      *
      * A sideband stream is a device-specific mechanism for passing buffers
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
new file mode 100644
index 0000000..49ab34d
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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 <utils/Errors.h>
+
+namespace android::recoverymap {
+
+enum {
+    // status_t map for errors in the media framework
+    // OK or NO_ERROR or 0 represents no error.
+
+    // See system/core/include/utils/Errors.h
+    // System standard errors from -1 through (possibly) -133
+    //
+    // Errors with special meanings and side effects.
+    // INVALID_OPERATION:  Operation attempted in an illegal state (will try to signal to app).
+    // DEAD_OBJECT:        Signal from CodecBase to MediaCodec that MediaServer has died.
+    // NAME_NOT_FOUND:     Signal from CodecBase to MediaCodec that the component was not found.
+
+    // JPEGR errors
+    JPEGR_IO_ERROR_BASE                 = -10000,
+    ERROR_JPEGR_INVALID_INPUT_TYPE      = JPEGR_IO_ERROR_BASE,
+    ERROR_JPEGR_INVALID_OUTPUT_TYPE     = JPEGR_IO_ERROR_BASE - 1,
+    ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
+
+    JPEGR_RUNTIME_ERROR_BASE            = -20000,
+    ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
+    ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
+    ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
+    ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
+};
+
+}  // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 15eca1e..31f1872 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
- #include <utils/Errors.h>
+#include "jpegrerrorcode.h"
 
 namespace android::recoverymap {
 
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 5d25722..67c23e9 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -24,7 +24,7 @@
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -40,7 +40,7 @@
    || uncompressed_yuv_420_image == nullptr
    || compressed_jpeg_image == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -53,7 +53,7 @@
   if (uncompressed_p010_image == nullptr
    || compressed_jpeg_image == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -62,7 +62,7 @@
 
 status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -72,7 +72,7 @@
 status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map,
                                         jr_uncompressed_ptr dest) {
   if (compressed_recovery_map == nullptr || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -82,7 +82,7 @@
 status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
                                         jr_compressed_ptr dest) {
   if (uncompressed_recovery_map == nullptr || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -95,7 +95,7 @@
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -108,7 +108,7 @@
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -117,7 +117,7 @@
 
 status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
@@ -130,7 +130,7 @@
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
    || dest == nullptr) {
-    return BAD_VALUE;
+    return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   // TBD
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index b075080..c345385 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -20,10 +20,15 @@
 // from nativewindow/includes/system/window.h
 // (not to be confused with the compatibility-only window.h from system/core/includes)
 #include <system/window.h>
+#include <android/native_window_aidl.h>
 
 #include <private/android/AHardwareBufferHelpers.h>
 
+#include <log/log.h>
 #include <ui/GraphicBuffer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+#include <android/binder_libbinder.h>
 
 using namespace android;
 
@@ -59,6 +64,13 @@
             return false;
     }
 }
+static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) {
+    return Surface::getIGraphicBufferProducer(window);
+}
+
+static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) {
+    return Surface::getSurfaceControlHandle(window);
+}
 
 /**************************************************************************************************
  * NDK
@@ -350,6 +362,42 @@
     return native_window_set_auto_prerotation(window, autoPrerotation);
 }
 
+binder_status_t ANativeWindow_readFromParcel(
+        const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
+    const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+
+    // Use a android::view::Surface to unparcel the window
+    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+    status_t ret = shimSurface->readFromParcel(nativeParcel);
+    if (ret != OK) {
+        ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
+        return STATUS_BAD_VALUE;
+    }
+    sp<Surface> surface = sp<Surface>::make(
+            shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle);
+    ANativeWindow* anw = surface.get();
+    ANativeWindow_acquire(anw);
+    *outWindow = anw;
+    return STATUS_OK;
+}
+
+binder_status_t ANativeWindow_writeToParcel(
+        ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) {
+    int value;
+    int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
+    if (err != OK || value != NATIVE_WINDOW_SURFACE) {
+        ALOGE("Error: ANativeWindow is not backed by Surface");
+        return STATUS_BAD_VALUE;
+    }
+    // Use a android::view::Surface to parcelize the window
+    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+    shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window);
+    shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window);
+
+    Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+    return shimSurface->writeToParcel(nativeParcel);
+}
+
 /**************************************************************************************************
  * apex-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 3b58265..bc0bfc5 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -110,9 +110,11 @@
     static_libs: [
         "libarect",
         "libgrallocusage",
+        "libgui_aidl_static",
     ],
 
     header_libs: [
+        "libgui_headers",
         "libarect_headers",
         "libnativebase_headers",
         "libnativewindow_headers",
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 281ec52..a27e3dd 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -376,7 +376,7 @@
         __INTRODUCED_IN(__ANDROID_API_U__);
 
 #ifdef __cplusplus
-};
+}
 #endif
 
 #endif // ANDROID_NATIVE_WINDOW_H
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
new file mode 100644
index 0000000..a252245
--- /dev/null
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2022 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.
+ */
+
+/**
+ * @file native_window_aidl.h
+ * @brief NativeWindow NDK AIDL glue code
+ */
+
+/**
+ * @addtogroup ANativeWindow
+ *
+ * Parcelable support for ANativeWindow. Can be used with libbinder_ndk
+ *
+ * @{
+ */
+
+#ifndef ANDROID_NATIVE_WINDOW_AIDL_H
+#define ANDROID_NATIVE_WINDOW_AIDL_H
+
+#include <android/binder_parcel.h>
+#include <android/native_window.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Read an ANativeWindow from a AParcel. The output buffer will have an
+ * initial reference acquired and will need to be released with
+ * ANativeWindow_release.
+ *
+ * Available since API level 34.
+ *
+ * \return STATUS_OK on success
+ *         STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an
+ *                          issue deserializing (eg, corrupted parcel)
+ *         STATUS_BAD_TYPE if the parcel's current data position is not that of
+ *                         an ANativeWindow type
+ *         STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t ANativeWindow_readFromParcel(const AParcel* _Nonnull parcel,
+        ANativeWindow* _Nullable* _Nonnull outWindow) __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
+ * Write an ANativeWindow to an AParcel.
+ *
+ * Available since API level 34.
+ *
+ * \return STATUS_OK on success.
+ *         STATUS_BAD_VALUE if either buffer or parcel is null, or if the ANativeWindow*
+ *                          fails to serialize (eg, internally corrupted)
+ *         STATUS_NO_MEMORY if the parcel runs out of space to store the buffer & is
+ *                          unable to allocate more
+ *         STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t ANativeWindow_writeToParcel(ANativeWindow* _Nonnull window,
+        AParcel* _Nonnull parcel) __INTRODUCED_IN(__ANDROID_API_U__);
+
+__END_DECLS
+
+// Only enable the AIDL glue helper if this is C++
+#ifdef __cplusplus
+
+namespace aidl::android::hardware {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * Takes ownership of the ANativeWindow* given to it in reset() and will automatically
+ * destroy it in the destructor, similar to a smart pointer container
+ */
+class NativeWindow {
+public:
+    NativeWindow() noexcept {}
+    explicit NativeWindow(ANativeWindow* _Nullable window) {
+        reset(window);
+    }
+
+    explicit NativeWindow(NativeWindow&& other) noexcept {
+        mWindow = other.release(); // steal ownership from r-value
+    }
+
+    ~NativeWindow() {
+        reset();
+    }
+
+    binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+        reset();
+        return ANativeWindow_readFromParcel(parcel, &mWindow);
+    }
+
+    binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+        if (!mWindow) {
+            return STATUS_BAD_VALUE;
+        }
+        return ANativeWindow_writeToParcel(mWindow, parcel);
+    }
+
+    /**
+     * Destroys any currently owned ANativeWindow* and takes ownership of the given
+     * ANativeWindow*
+     *
+     * @param buffer The buffer to take ownership of
+     */
+    void reset(ANativeWindow* _Nullable window = nullptr) noexcept {
+        if (mWindow) {
+            ANativeWindow_release(mWindow);
+            mWindow = nullptr;
+        }
+        if (window != nullptr) {
+            ANativeWindow_acquire(window);
+        }
+        mWindow = window;
+    }
+    inline ANativeWindow* _Nullable operator-> () const { return mWindow;  }
+    inline ANativeWindow* _Nullable get() const { return mWindow; }
+    inline explicit operator bool () const { return mWindow != nullptr; }
+
+    NativeWindow& operator=(NativeWindow&& other) noexcept {
+        mWindow = other.release(); // steal ownership from r-value
+        return *this;
+    }
+
+    /**
+     * Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
+     * is released.
+     * @return ANativeWindow* or null if this was empty
+     */
+    [[nodiscard]] ANativeWindow* _Nullable release() noexcept {
+        ANativeWindow* _Nullable ret = mWindow;
+        mWindow = nullptr;
+        return ret;
+    }
+private:
+    ANativeWindow* _Nullable mWindow = nullptr;
+    NativeWindow(const NativeWindow &other) = delete;
+    NativeWindow& operator=(const NativeWindow &other) = delete;
+};
+
+} // aidl::android::hardware
+  //
+namespace aidl::android::view {
+    using Surface = aidl::android::hardware::NativeWindow;
+}
+
+#endif // __cplusplus
+
+#endif // ANDROID_NATIVE_WINDOW_AIDL_H
+
+/** @} */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index ce108b6..76d23fa 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -57,6 +57,8 @@
     ANativeWindow_setUsage; # llndk
     ANativeWindow_tryAllocateBuffers; # introduced=30
     ANativeWindow_unlockAndPost;
+    ANativeWindow_readFromParcel; # introduced=UpsideDownCake
+    ANativeWindow_writeToParcel; # introduced=UpsideDownCake
   local:
     *;
 };
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 63a2418..9a6ebaa 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2092,7 +2092,7 @@
     }
 
     bool isSplit = shouldSplitTouch(tempTouchState, entry);
-    bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 &&
+    const bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 &&
             (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source ||
              tempTouchState.displayId != displayId);
 
@@ -2102,7 +2102,6 @@
     const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                              maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
-    bool wrongDevice = false;
     if (newGesture) {
         bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
         if (switchedDevice && tempTouchState.down && !down && !isHoverAction) {
@@ -2111,9 +2110,7 @@
                   displayId);
             // TODO: test multiple simultaneous input streams.
             outInjectionResult = InputEventInjectionResult::FAILED;
-            switchedDevice = false;
-            wrongDevice = true;
-            return touchedWindows;
+            return touchedWindows; // wrong device
         }
         tempTouchState.reset();
         tempTouchState.down = down;
@@ -2127,9 +2124,7 @@
               displayId);
         // TODO: test multiple simultaneous input streams.
         outInjectionResult = InputEventInjectionResult::FAILED;
-        switchedDevice = false;
-        wrongDevice = true;
-        return touchedWindows;
+        return touchedWindows; // wrong device
     }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index eb31046..a3f45cf 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -138,14 +138,4 @@
     return nullptr;
 }
 
-sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
-    for (const TouchedWindow& touchedWindow : windows) {
-        const auto& windowHandle = touchedWindow.windowHandle;
-        if (windowHandle->getToken() == token) {
-            return windowHandle;
-        }
-    }
-    return nullptr;
-}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index d324611..f6e9fb6 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -59,7 +59,6 @@
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
     bool isSlippery() const;
     sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
-    sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index d14b0f0..2110107 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3562,6 +3562,8 @@
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
                                                               bool hovering) {
+    LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
+                        "%s cannot be used when the device is not in POINTER mode.", __func__);
     std::list<NotifyArgs> out;
     int32_t metaState = getContext()->getGlobalMetaState();
 
@@ -3688,6 +3690,10 @@
     if (down || hovering) {
         mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
         mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+        mPointerSimple.displayId = displayId;
+        mPointerSimple.source = mSource;
+        mPointerSimple.lastCursorX = xCursorPosition;
+        mPointerSimple.lastCursorY = yCursorPosition;
     } else {
         mPointerSimple.reset();
     }
@@ -3696,10 +3702,25 @@
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags) {
-    mPointerSimple.currentCoords.clear();
-    mPointerSimple.currentProperties.clear();
-
-    return dispatchPointerSimple(when, readTime, policyFlags, false, false);
+    std::list<NotifyArgs> out;
+    if (mPointerSimple.down || mPointerSimple.hovering) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                       mPointerSimple.source, mPointerSimple.displayId, policyFlags,
+                                       AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+                                       metaState, mLastRawState.buttonState,
+                                       MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+                                       &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+                                       mOrientedXPrecision, mOrientedYPrecision,
+                                       mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
+                                       mPointerSimple.downTime,
+                                       /* videoFrames */ {}));
+        if (mPointerController != nullptr) {
+            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+        }
+    }
+    mPointerSimple.reset();
+    return out;
 }
 
 NotifyMotionArgs TouchInputMapper::dispatchMotion(
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index bab15cc..d5e4d5a 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -681,6 +681,12 @@
         // Time the pointer last went down.
         nsecs_t downTime;
 
+        // Values reported for the last pointer event.
+        uint32_t source;
+        int32_t displayId;
+        float lastCursorX;
+        float lastCursorY;
+
         void reset() {
             currentCoords.clear();
             currentProperties.clear();
@@ -689,6 +695,10 @@
             down = false;
             hovering = false;
             downTime = 0;
+            source = 0;
+            displayId = ADISPLAY_ID_NONE;
+            lastCursorX = 0.f;
+            lastCursorY = 0.f;
         }
     } mPointerSimple;
 
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 5d5bee7..1891205 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -108,13 +108,13 @@
 }
 
 void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) {
-    int32_t outKeyCode, outMetaState;
-    uint32_t outFlags;
+    int32_t keyCode, metaState;
+    uint32_t flags;
     if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(),
-                              0 /*metaState*/, &outKeyCode, &outMetaState, &outFlags) != OK) {
+                              0 /*metaState*/, &keyCode, &metaState, &flags) != OK) {
         return;
     }
-    switch (outKeyCode) {
+    switch (keyCode) {
         case AKEYCODE_STYLUS_BUTTON_PRIMARY:
             mBtnStylus = down;
             break;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 39f521b..0a5793d 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -10444,6 +10444,46 @@
     ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0);
 }
 
+TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Start a stylus gesture.
+    processKey(mapper, BTN_TOOL_PEN, 1);
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus
+    // gesture should be disabled.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->isActive = false;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 // --- JoystickInputMapperTest ---
 
 class JoystickInputMapperTest : public InputMapperTest {
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 892c07d..999c03f 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -49,7 +49,7 @@
         "android.hardware.graphics.composer@2.4",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 0ae8bf9..c1460cf 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -25,7 +25,7 @@
         "android.hardware.graphics.composer@2.4",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libbase",
         "libcutils",
         "libgui",
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 499cee6..0e1b775 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -94,7 +94,7 @@
     }
 }
 
-void Scheduler::setRefreshRateSelector(std::shared_ptr<RefreshRateSelector> selectorPtr) {
+void Scheduler::setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
     // The current RefreshRateSelector instance may outlive this call, so unbind its idle timer.
     {
         // mRefreshRateSelectorLock is not locked here to avoid the deadlock
@@ -126,13 +126,12 @@
     mRefreshRateSelector->startIdleTimer();
 }
 
-void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
-    if (display->isPrimary()) {
-        mLeaderDisplayId = display->getPhysicalId();
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+    if (!mLeaderDisplayId) {
+        mLeaderDisplayId = displayId;
     }
 
-    const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
-    ALOGE_IF(!ok, "%s: Duplicate display", __func__);
+    mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
@@ -140,7 +139,7 @@
         mLeaderDisplayId.reset();
     }
 
-    mDisplays.erase(displayId);
+    mRefreshRateSelectors.erase(displayId);
 }
 
 void Scheduler::run() {
@@ -711,10 +710,9 @@
 
     const auto globalSignals = makeGlobalSignals();
 
-    for (const auto& [id, display] : mDisplays) {
+    for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
         auto rankedRefreshRates =
-                display->holdRefreshRateSelector()
-                        ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
+                selectorPtr->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
 
         for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
             const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
@@ -733,7 +731,7 @@
 
     // Find the first refresh rate common to all displays.
     while (maxScoreIt != refreshRateTallies.cend() &&
-           maxScoreIt->second.displayCount != mDisplays.size()) {
+           maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) {
         ++maxScoreIt;
     }
 
@@ -742,7 +740,8 @@
         for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) {
             const auto [fps, tally] = *it;
 
-            if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) {
+            if (tally.displayCount == mRefreshRateSelectors.size() &&
+                tally.score > maxScoreIt->second.score) {
                 maxScoreIt = it;
             }
         }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 39c41b9..901cf74 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -39,7 +39,6 @@
 
 #include "Display/DisplayMap.h"
 #include "Display/DisplayModeRequest.h"
-#include "DisplayDevice.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "LayerHistory.h"
@@ -107,10 +106,11 @@
     virtual ~Scheduler();
 
     void startTimers();
-    void setRefreshRateSelector(std::shared_ptr<RefreshRateSelector>)
-            EXCLUDES(mRefreshRateSelectorLock);
 
-    void registerDisplay(sp<const DisplayDevice>);
+    using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
+    void setRefreshRateSelector(RefreshRateSelectorPtr) EXCLUDES(mRefreshRateSelectorLock);
+
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr);
     void unregisterDisplay(PhysicalDisplayId);
 
     void run();
@@ -299,8 +299,7 @@
             EXCLUDES(mRefreshRateSelectorLock);
     android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
 
-    std::shared_ptr<RefreshRateSelector> holdRefreshRateSelector() const
-            EXCLUDES(mRefreshRateSelectorLock) {
+    RefreshRateSelectorPtr holdRefreshRateSelector() const EXCLUDES(mRefreshRateSelectorLock) {
         std::scoped_lock lock(mRefreshRateSelectorLock);
         return mRefreshRateSelector;
     }
@@ -336,7 +335,7 @@
 
     mutable std::mutex mPolicyLock;
 
-    display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
+    display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors;
     std::optional<PhysicalDisplayId> mLeaderDisplayId;
 
     struct Policy {
@@ -359,8 +358,9 @@
         std::optional<ModeChangedParams> cachedModeChangedParams;
     } mPolicy GUARDED_BY(mPolicyLock);
 
+    // TODO(b/255635821): Remove this by instead looking up the `mLeaderDisplayId` selector.
     mutable std::mutex mRefreshRateSelectorLock;
-    std::shared_ptr<RefreshRateSelector> mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock);
+    RefreshRateSelectorPtr mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock);
 
     std::mutex mVsyncTimelineLock;
     std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 89d905a..aa930bc 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2937,11 +2937,15 @@
                                                  displaySurface, producer);
 
     if (mScheduler && !display->isVirtual()) {
+        auto selectorPtr = display->holdRefreshRateSelector();
+
         // Display modes are reloaded on hotplug reconnect.
         if (display->isPrimary()) {
-            mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector());
+            mScheduler->setRefreshRateSelector(selectorPtr);
         }
-        mScheduler->registerDisplay(display);
+
+        const auto displayId = display->getPhysicalId();
+        mScheduler->registerDisplay(displayId, std::move(selectorPtr));
         dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
     }
 
@@ -2994,8 +2998,6 @@
             display->disconnect();
             if (display->isVirtual()) {
                 releaseVirtualDisplay(display->getVirtualId());
-            } else {
-                mScheduler->unregisterDisplay(display->getPhysicalId());
             }
         }
 
@@ -3409,8 +3411,8 @@
         }
 
         mScheduler->createVsyncSchedule(features);
-        mScheduler->setRefreshRateSelector(std::move(selectorPtr));
-        mScheduler->registerDisplay(display);
+        mScheduler->setRefreshRateSelector(selectorPtr);
+        mScheduler->registerDisplay(display->getPhysicalId(), std::move(selectorPtr));
     }
     setVsyncEnabled(false);
     mScheduler->startTimers();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5daa398..d88da4d 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -150,7 +150,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libaidlcommonsupport",
         "libcompositionengine_mocks",
         "libcompositionengine",
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 066083f..ea4666e 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -20,7 +20,6 @@
 
 #include <mutex>
 
-#include "FakeDisplayInjector.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "TestableScheduler.h"
@@ -41,7 +40,6 @@
 
 using MockEventThread = android::mock::EventThread;
 using MockLayer = android::mock::MockLayer;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
 class SchedulerTest : public testing::Test {
 protected:
@@ -90,10 +88,6 @@
     sp<MockEventThreadConnection> mEventThreadConnection;
 
     TestableSurfaceFlinger mFlinger;
-    Hwc2::mock::PowerAdvisor mPowerAdvisor;
-    sp<android::mock::NativeWindow> mNativeWindow = sp<android::mock::NativeWindow>::make();
-
-    FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
 };
 
 SchedulerTest::SchedulerTest() {
@@ -240,14 +234,11 @@
 }
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
-    const auto display = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
-            },
-            {.displayId = kDisplayId1});
+    const auto selectorPtr =
+            std::make_shared<RefreshRateSelector>(kDisplay1Modes, kDisplay1Mode60->getId());
 
-    mScheduler->registerDisplay(display);
-    mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector());
+    mScheduler->registerDisplay(kDisplayId1, selectorPtr);
+    mScheduler->setRefreshRateSelector(selectorPtr);
 
     const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
     EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
@@ -269,13 +260,9 @@
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
-    const auto display = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
-            },
-            {.displayId = kDisplayId1});
-
-    mScheduler->registerDisplay(display);
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
 
     std::vector<RefreshRateSelector::LayerRequirement> layers =
             std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
@@ -314,23 +301,16 @@
     EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
 
     mScheduler->unregisterDisplay(kDisplayId1);
-    EXPECT_TRUE(mScheduler->mutableDisplays().empty());
+    EXPECT_FALSE(mScheduler->hasRefreshRateSelectors());
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
-    const auto display1 = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
-            },
-            {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true});
-    const auto display2 = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId());
-            },
-            {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false});
-
-    mScheduler->registerDisplay(display1);
-    mScheduler->registerDisplay(display2);
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
 
     using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
     TestableScheduler::DisplayModeChoiceMap expectedChoices;
@@ -380,13 +360,10 @@
     }
     {
         // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
-        const auto display3 = mFakeDisplayInjector.injectInternalDisplay(
-                [&](FakeDisplayDeviceInjector& injector) {
-                    injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId());
-                },
-                {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false});
-
-        mScheduler->registerDisplay(display3);
+        mScheduler
+                ->registerDisplay(kDisplayId3,
+                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                        kDisplay3Mode60->getId()));
 
         const GlobalSignals globalSignals = {.touch = true};
         mScheduler->replaceTouchTimer(10);
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 2814d38..95c9915 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,15 +32,13 @@
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(std::shared_ptr<RefreshRateSelector> selectorPtr,
-                      ISchedulerCallback& callback)
+    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
                               std::make_unique<mock::VSyncTracker>(), std::move(selectorPtr),
                               callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
-                      std::unique_ptr<VSyncTracker> tracker,
-                      std::shared_ptr<RefreshRateSelector> selectorPtr,
+                      std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection) {
         mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
@@ -68,16 +66,15 @@
     auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
 
-    auto& mutableLayerHistory() { return mLayerHistory; }
+    auto refreshRateSelector() { return holdRefreshRateSelector(); }
+    bool hasRefreshRateSelectors() const { return !mRefreshRateSelectors.empty(); }
 
-    auto& mutableDisplays() { return mDisplays; }
+    auto& mutableLayerHistory() { return mLayerHistory; }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
     }
 
-    auto refreshRateSelector() { return holdRefreshRateSelector(); }
-
     size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size();
     }
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index ff79ce0..35c037c 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -842,7 +842,8 @@
             sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
             mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
             if (mFlinger.scheduler()) {
-                mFlinger.scheduler()->registerDisplay(display);
+                mFlinger.scheduler()->registerDisplay(display->getPhysicalId(),
+                                                      display->holdRefreshRateSelector());
             }
 
             DisplayDeviceState state;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 439f6f4..5f749df 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -23,6 +23,7 @@
 
 using android::binder::Status;
 using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
 
 using namespace android::hardware::power;
 
@@ -40,6 +41,7 @@
     MOCK_METHOD(std::string, getInterfaceHash, (), (override));
     MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
     MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
+    MOCK_METHOD(Status, sendHint, (SessionHint), (override));
 };
 
 } // namespace android::Hwc2::mock