Merge "Rename SkiaGpuContext::isAbandoned to isAbandonedOrDeviceLost" into main
diff --git a/cmds/bugreport/OWNERS b/cmds/bugreport/OWNERS
index 5f56531..41bfd26 100644
--- a/cmds/bugreport/OWNERS
+++ b/cmds/bugreport/OWNERS
@@ -1,5 +1,5 @@
 set noparent
 
-gavincorkery@google.com
+ronish@google.com
 nandana@google.com
 jsharkey@android.com
diff --git a/cmds/bugreportz/OWNERS b/cmds/bugreportz/OWNERS
index 5f56531..41bfd26 100644
--- a/cmds/bugreportz/OWNERS
+++ b/cmds/bugreportz/OWNERS
@@ -1,5 +1,5 @@
 set noparent
 
-gavincorkery@google.com
+ronish@google.com
 nandana@google.com
 jsharkey@android.com
diff --git a/cmds/dumpstate/OWNERS b/cmds/dumpstate/OWNERS
index ab81ecf..c24bf39 100644
--- a/cmds/dumpstate/OWNERS
+++ b/cmds/dumpstate/OWNERS
@@ -1,6 +1,6 @@
 set noparent
 
-gavincorkery@google.com
+ronish@google.com
 nandana@google.com
 jsharkey@android.com
 smoreland@google.com
\ No newline at end of file
diff --git a/cmds/dumpsys/OWNERS b/cmds/dumpsys/OWNERS
index 97a63ca..03143ae 100644
--- a/cmds/dumpsys/OWNERS
+++ b/cmds/dumpsys/OWNERS
@@ -1,6 +1,6 @@
 set noparent
 
-gavincorkery@google.com
+ronish@google.com
 nandana@google.com
 jsharkey@android.com
 
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 870e8eb..0c1feb8 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -731,16 +731,17 @@
                                   [hashChain](const auto& ret) { *hashChain = std::move(ret); });
         if (!hashRet.isOk()) {
             handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+            break; // skip getHashChain
         }
         if (static_cast<size_t>(hashIndex) >= hashChain->size()) {
             handleError(BAD_IMPL,
                         "interfaceChain indicates position " + std::to_string(hashIndex) +
                                 " but getHashChain returns " + std::to_string(hashChain->size()) +
                                 " hashes");
-        } else {
-            auto&& hashArray = (*hashChain)[hashIndex];
-            entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
+            break; // skip getHashChain
         }
+        auto&& hashArray = (*hashChain)[hashIndex];
+        entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
     } while (0);
     if (status == OK) {
         entry->serviceStatus = ServiceStatus::ALIVE;
diff --git a/include/android/OWNERS b/include/android/OWNERS
index 38f9c55..fad8c1b 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -1 +1,5 @@
-per-file input.h, keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+per-file input.h,keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+
+# Window manager
+per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h
index ba5f6f2..92fe9b6 100644
--- a/include/android/input_transfer_token_jni.h
+++ b/include/android/input_transfer_token_jni.h
@@ -20,8 +20,9 @@
 /**
  * @file input_transfer_token_jni.h
  */
-#ifndef ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
-#define ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
+
+#pragma once
+
 #include <sys/cdefs.h>
 #include <jni.h>
 
@@ -60,9 +61,8 @@
  *
  * Available since API level 35.
  */
-void AInputTransferToken_release(AInputTransferToken* _Nonnull aInputTransferToken)
+void AInputTransferToken_release(AInputTransferToken* _Nullable aInputTransferToken)
         __INTRODUCED_IN(__ANDROID_API_V__);
 
 __END_DECLS
-#endif // ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
 /** @} */
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
new file mode 100644
index 0000000..bdc5249
--- /dev/null
+++ b/include/android/surface_control_input_receiver.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 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 NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file surface_control_input_receiver.h
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <android/input.h>
+#include <android/surface_control.h>
+#include <android/input_transfer_token_jni.h>
+
+__BEGIN_DECLS
+
+/**
+ * The AInputReceiver_onMotionEvent callback is invoked when the registered input channel receives
+ * a motion event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param motionEvent The motion event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context,
+                                             AInputEvent *_Nonnull motionEvent)
+                                            __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives
+ * a key event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param keyEvent The key event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context,
+                                          AInputEvent *_Nonnull keyEvent)
+                                          __INTRODUCED_IN(__ANDROID_API_V__);
+
+struct AInputReceiverCallbacks;
+
+struct AInputReceiver;
+
+/**
+ * The InputReceiver that holds the reference to the registered input channel. This must be released
+ * using AInputReceiver_release
+ *
+ * Available since API level 35.
+ */
+typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive batched input event. For
+ * those events that are batched, the invocation will happen once per AChoreographer frame, and
+ * other input events will be delivered immediately.
+ *
+ * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are
+ * received batched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aChoreographer         The AChoreographer used for batching. This should match the
+ *                               rendering AChoreographer.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createBatchedInputReceiver(AChoreographer* _Nonnull aChoreographer,
+                                        const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                        const ASurfaceControl* _Nonnull aSurfaceControl,
+                                        AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive every input event.
+ * This is different from AInputReceiver_createBatchedInputReceiver in that the input events are
+ * received unbatched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aLooper                The looper to use when invoking callbacks.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createUnbatchedInputReceiver(ALooper* _Nonnull aLooper,
+                                         const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                         const ASurfaceControl* _Nonnull aSurfaceControl,
+                                         AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                         __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Returns the AInputTransferToken that can be used to transfer touch gesture to or from other
+ * windows. This InputTransferToken is associated with the SurfaceControl that registered an input
+ * receiver and can be used with the host token for things like transfer touch gesture via
+ * WindowManager#transferTouchGesture().
+ *
+ * This must be released with AInputTransferToken_release.
+ *
+ * \param aInputReceiver The inputReceiver object to retrieve the AInputTransferToken for.
+ *
+ * Available since API level 35.
+ */
+const AInputTransferToken *_Nonnull
+AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Unregisters the input channel and deletes the AInputReceiver. This must be called on the same
+ * looper thread it was created with.
+ *
+ * \param aInputReceiver The inputReceiver object to release.
+ *
+ * Available since API level 35.
+ */
+void
+AInputReceiver_release(AInputReceiver *_Nullable aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver.
+ * This must be released using AInputReceiverCallbacks_release
+ *
+ * \param context Optional context provided by the client that will be passed into the callbacks.
+ *
+ * Available since API level 35.
+ */
+AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable context)
+                                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Releases the AInputReceiverCallbacks. This must be called on the same
+ * looper thread the AInputReceiver was created with. The receiver will not invoke any callbacks
+ * once it's been released.
+ *
+ * Available since API level 35
+ */
+void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nullable callbacks)
+                                     __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onMotionEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The motion event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setMotionEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                AInputReceiver_onMotionEvent _Nonnull onMotionEvent)
+                                                __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onKeyEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The key event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setKeyEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                 AInputReceiver_onKeyEvent _Nonnull onKeyEvent)
+                                                 __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 7e6ae9b..9e48b08 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -29,8 +29,8 @@
 class InputConsumerCallbacks {
 public:
     virtual ~InputConsumerCallbacks(){};
-    virtual void onKeyEvent(KeyEvent&& event, uint32_t seq) = 0;
-    virtual void onMotionEvent(MotionEvent&& event, uint32_t seq) = 0;
+    virtual void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) = 0;
+    virtual void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) = 0;
     /**
      * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
      * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
@@ -38,10 +38,10 @@
      * @param pendingBatchSource the source of the pending batch.
      */
     virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
-    virtual void onFocusEvent(FocusEvent&& event, uint32_t seq) = 0;
-    virtual void onCaptureEvent(CaptureEvent&& event, uint32_t seq) = 0;
-    virtual void onDragEvent(DragEvent&& event, uint32_t seq) = 0;
-    virtual void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) = 0;
+    virtual void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) = 0;
+    virtual void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) = 0;
+    virtual void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) = 0;
+    virtual void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) = 0;
 };
 
 /**
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index 69b11c0..7b58046 100644
--- a/libs/binder/IBatteryStats.cpp
+++ b/libs/binder/IBatteryStats.cpp
@@ -66,14 +66,14 @@
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteStopAudio(int uid) {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteResetVideo() {
@@ -85,7 +85,7 @@
     virtual void noteResetAudio() {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
-        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteFlashlightOn(int uid) {
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index bf7a0ba..e2ede3f 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -258,11 +258,24 @@
     }
 }
 
+void ABBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                                 void* /* cookie */) {
+    LOG_ALWAYS_FATAL("Should not reach this. Can't linkToDeath local binders.");
+}
+
 ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
     : AIBinder(nullptr /*clazz*/), mRemote(binder) {
     LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 }
-ABpBinder::~ABpBinder() {}
+
+ABpBinder::~ABpBinder() {
+    for (auto& recip : mDeathRecipients) {
+        sp<AIBinder_DeathRecipient> strongRecip = recip.recipient.promote();
+        if (strongRecip) {
+            strongRecip->pruneThisTransferEntry(getBinder(), recip.cookie);
+        }
+    }
+}
 
 sp<AIBinder> ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android::IBinder>& binder) {
     if (binder == nullptr) {
@@ -301,6 +314,12 @@
     return ret;
 }
 
+void ABpBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                  void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.emplace_back(recipient, cookie);
+}
+
 struct AIBinder_Weak {
     wp<AIBinder> binder;
 };
@@ -426,6 +445,17 @@
     LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
 }
 
+void AIBinder_DeathRecipient::pruneThisTransferEntry(const sp<IBinder>& who, void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                                          [&](const sp<TransferDeathRecipient>& tdr) {
+                                              auto tdrWho = tdr->getWho();
+                                              return tdrWho != nullptr && tdrWho.promote() == who &&
+                                                     cookie == tdr->getCookie();
+                                          }),
+                           mDeathRecipients.end());
+}
+
 void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
     mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
                                           [](const sp<TransferDeathRecipient>& tdr) {
@@ -554,8 +584,11 @@
         return STATUS_UNEXPECTED_NULL;
     }
 
-    // returns binder_status_t
-    return recipient->linkToDeath(binder->getBinder(), cookie);
+    binder_status_t ret = recipient->linkToDeath(binder->getBinder(), cookie);
+    if (ret == STATUS_OK) {
+        binder->addDeathRecipient(recipient, cookie);
+    }
+    return ret;
 }
 
 binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 9d5368f..f5b738c 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -51,6 +51,8 @@
         ::android::sp<::android::IBinder> binder = const_cast<AIBinder*>(this)->getBinder();
         return binder->remoteBinder() != nullptr;
     }
+    virtual void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                   void* cookie) = 0;
 
    private:
     // AIBinder instance is instance of this class for a local object. In order to transact on a
@@ -78,6 +80,8 @@
     ::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override;
     ::android::status_t onTransact(uint32_t code, const ::android::Parcel& data,
                                    ::android::Parcel* reply, binder_flags_t flags) override;
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                           void* /* cookie */) override;
 
    private:
     ABBinder(const AIBinder_Class* clazz, void* userData);
@@ -106,12 +110,20 @@
 
     bool isServiceFuzzing() const { return mServiceFuzzing; }
     void setServiceFuzzing() { mServiceFuzzing = true; }
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                           void* cookie) override;
 
    private:
     friend android::sp<ABpBinder>;
     explicit ABpBinder(const ::android::sp<::android::IBinder>& binder);
     ::android::sp<::android::IBinder> mRemote;
     bool mServiceFuzzing = false;
+    struct DeathRecipientInfo {
+        android::wp<AIBinder_DeathRecipient> recipient;
+        void* cookie;
+    };
+    std::mutex mDeathRecipientsMutex;
+    std::vector<DeathRecipientInfo> mDeathRecipients;
 };
 
 struct AIBinder_Class {
@@ -183,6 +195,7 @@
     binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie);
     binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie);
     void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked);
+    void pruneThisTransferEntry(const ::android::sp<::android::IBinder>&, void* cookie);
 
    private:
     // When the user of this API deletes a Bp object but not the death recipient, the
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index 3aacbe9..d570eab 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -22,14 +22,20 @@
 #include <set>
 #include <sstream>
 
-namespace aidl::android::os {
-
+// Include llndk-versioning.h only for vendor build as it is not available for NDK headers.
 #if defined(__ANDROID_VENDOR__)
-#define AT_LEAST_V_OR_202404 constexpr(__ANDROID_VENDOR_API__ >= 202404)
-#else
-// TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
-#define AT_LEAST_V_OR_202404 (__builtin_available(android __ANDROID_API_FUTURE__, *))
+#include <android/llndk-versioning.h>
+#else  // __ANDROID_VENDOR__
+#if defined(API_LEVEL_AT_LEAST)
+// Redefine API_LEVEL_AT_LEAST here to replace the version to __ANDROID_API_FUTURE__ as a workaround
+#undef API_LEVEL_AT_LEAST
 #endif
+// TODO(b/322384429) switch this __ANDROID_API_FUTURE__ to sdk_api_level when V is finalized
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+    (__builtin_available(android __ANDROID_API_FUTURE__, *))
+#endif  // __ANDROID_VENDOR__
+
+namespace aidl::android::os {
 
 /**
  * Wrapper class that enables interop with AIDL NDK generation
@@ -39,7 +45,7 @@
 class PersistableBundle {
    public:
     PersistableBundle() noexcept {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             mPBundle = APersistableBundle_new();
         }
     }
@@ -49,13 +55,13 @@
     PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle(const PersistableBundle& other) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
     }
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle& operator=(const PersistableBundle& other) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
         return *this;
@@ -65,7 +71,7 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_readFromParcel(parcel, &mPBundle);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -76,7 +82,7 @@
         if (!mPBundle) {
             return STATUS_BAD_VALUE;
         }
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_writeToParcel(mPBundle, parcel);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -91,7 +97,7 @@
      */
     void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
         if (mPBundle) {
-            if AT_LEAST_V_OR_202404 {
+            if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
                 APersistableBundle_delete(mPBundle);
             }
             mPBundle = nullptr;
@@ -104,7 +110,7 @@
      * what should be used to check for equality.
      */
     bool deepEquals(const PersistableBundle& rhs) const {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_isEqual(get(), rhs.get());
         } else {
             return false;
@@ -143,7 +149,7 @@
     inline std::string toString() const {
         if (!mPBundle) {
             return "<PersistableBundle: null>";
-        } else if AT_LEAST_V_OR_202404 {
+        } else if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             std::ostringstream os;
             os << "<PersistableBundle: ";
             os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
@@ -154,7 +160,7 @@
     }
 
     int32_t size() const {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_size(mPBundle);
         } else {
             return 0;
@@ -162,7 +168,7 @@
     }
 
     int32_t erase(const std::string& key) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_erase(mPBundle, key.c_str());
         } else {
             return 0;
@@ -170,37 +176,37 @@
     }
 
     void putBoolean(const std::string& key, bool val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
         }
     }
 
     void putInt(const std::string& key, int32_t val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putInt(mPBundle, key.c_str(), val);
         }
     }
 
     void putLong(const std::string& key, int64_t val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putLong(mPBundle, key.c_str(), val);
         }
     }
 
     void putDouble(const std::string& key, double val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putDouble(mPBundle, key.c_str(), val);
         }
     }
 
     void putString(const std::string& key, const std::string& val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
         }
     }
 
     void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             // std::vector<bool> has no ::data().
             int32_t num = vec.size();
             if (num > 0) {
@@ -217,7 +223,7 @@
     }
 
     void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
@@ -225,7 +231,7 @@
         }
     }
     void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
@@ -233,7 +239,7 @@
         }
     }
     void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
@@ -241,7 +247,7 @@
         }
     }
     void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t num = vec.size();
             if (num > 0) {
                 char** inVec = (char**)malloc(num * sizeof(char*));
@@ -256,13 +262,13 @@
         }
     }
     void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
         }
     }
 
     bool getBoolean(const std::string& key, bool* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -270,7 +276,7 @@
     }
 
     bool getInt(const std::string& key, int32_t* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_getInt(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -278,7 +284,7 @@
     }
 
     bool getLong(const std::string& key, int64_t* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_getLong(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -286,7 +292,7 @@
     }
 
     bool getDouble(const std::string& key, double* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -298,7 +304,7 @@
     }
 
     bool getString(const std::string& key, std::string* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             char* outString = nullptr;
             bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
                                                     &stringAllocator, nullptr);
@@ -316,7 +322,7 @@
                                                    const char* _Nonnull, T* _Nullable, int32_t),
                         const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
                         std::vector<T>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t bytes = 0;
             // call first with nullptr to get required size in bytes
             bytes = getVec(pBundle, key, nullptr, 0);
@@ -338,28 +344,28 @@
     }
 
     bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
                                         vec);
         }
         return false;
     }
     bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
                                           key.c_str(), vec);
         }
@@ -384,7 +390,7 @@
     }
 
     bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
                                                                &stringAllocator, nullptr);
             if (bytes > 0) {
@@ -401,7 +407,7 @@
     }
 
     bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             APersistableBundle* bundle = nullptr;
             bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
             if (ret) {
@@ -433,77 +439,77 @@
     }
 
     std::set<std::string> getBooleanKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getIntKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getLongKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getStringKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getBooleanVectorKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntVectorKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongVectorKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleVectorKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringVectorKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getPersistableBundleKeys() {
-        if AT_LEAST_V_OR_202404 {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
             return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
         } else {
             return {};
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
index 1247e8e..42ae15a 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -20,8 +20,10 @@
 #if defined(__ANDROID_VENDOR__)
 #include <android/llndk-versioning.h>
 #else
-#define __INTRODUCED_IN_LLNDK(x)
+#if !defined(__INTRODUCED_IN_LLNDK)
+#define __INTRODUCED_IN_LLNDK(level) __attribute__((annotate("introduced_in_llndk=" #level)))
 #endif
+#endif  // __ANDROID_VENDOR__
 #include <stdbool.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index a905dff..52edae4 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -18,6 +18,7 @@
 
 #include <android/binder_ibinder.h>
 #include <android/binder_status.h>
+#include <android/llndk-versioning.h>
 #include <sys/cdefs.h>
 
 __BEGIN_DECLS
@@ -252,9 +253,8 @@
  * \return the result of dlopen of the specified HAL
  */
 void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
-                                                 int flag)
-        // TODO(b/302113279) use __INTRODUCED_LLNDK for vendor variants
-        __INTRODUCED_IN(__ANDROID_API_V__);
+                                                 int flag) __INTRODUCED_IN(__ANDROID_API_V__)
+        __INTRODUCED_IN_LLNDK(202404);
 
 /**
  * Prevent lazy services without client from shutting down their process
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 3ee36cd..ca92727 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -25,6 +25,7 @@
 
 const char* IFoo::kSomeInstanceName = "libbinder_ndk-test-IFoo";
 const char* IFoo::kInstanceNameToDieFor = "libbinder_ndk-test-IFoo-to-die";
+const char* IFoo::kInstanceNameToDieFor2 = "libbinder_ndk-test-IFoo-to-die2";
 const char* IFoo::kIFooDescriptor = "my-special-IFoo-class";
 
 struct IFoo_Class_Data {
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 0a562f0..0cdd50b 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -27,6 +27,7 @@
    public:
     static const char* kSomeInstanceName;
     static const char* kInstanceNameToDieFor;
+    static const char* kInstanceNameToDieFor2;
     static const char* kIFooDescriptor;
 
     static AIBinder_Class* kClass;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 966ec95..ce63b82 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -536,6 +536,7 @@
     bool deathReceived = false;
 
     std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
         std::cerr << "Binder died (as requested)." << std::endl;
         deathReceived = true;
         deathCv.notify_one();
@@ -547,6 +548,7 @@
     bool wasDeathReceivedFirst = false;
 
     std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
         std::cerr << "Binder unlinked (as requested)." << std::endl;
         wasDeathReceivedFirst = deathReceived;
         unlinkReceived = true;
@@ -560,7 +562,6 @@
 
     EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(cookie)));
 
-    // the binder driver should return this if the service dies during the transaction
     EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
 
     foo = nullptr;
@@ -579,6 +580,123 @@
     binder = nullptr;
 }
 
+TEST(NdkBinder, DeathRecipientDropBinderNoDeath) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    // keep the death recipient around
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    {
+        AIBinder* binder;
+        sp<IFoo> foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+        ASSERT_NE(nullptr, foo.get());
+        ASSERT_NE(nullptr, binder);
+
+        DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+        // let the sp<IFoo> and AIBinder fall out of scope
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+    }
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_FALSE(deathCv.wait_for(lockDeath, 100ms, [&] { return deathReceived; }));
+        EXPECT_FALSE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_FALSE(wasDeathReceivedFirst);
+    }
+}
+
+TEST(NdkBinder, DeathRecipientDropBinderOnDied) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    sp<IFoo> foo;
+    AIBinder* binder;
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+    EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+
+    EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; }));
+        EXPECT_TRUE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 100ms, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_TRUE(wasDeathReceivedFirst);
+    }
+}
+
 TEST(NdkBinder, RetrieveNonNdkService) {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -958,6 +1076,10 @@
     }
     if (fork() == 0) {
         prctl(PR_SET_PDEATHSIG, SIGHUP);
+        return manualThreadPoolService(IFoo::kInstanceNameToDieFor2);
+    }
+    if (fork() == 0) {
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
         return manualPollingService(IFoo::kSomeInstanceName);
     }
     if (fork() == 0) {
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 54290cd..0c8f3fa 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -425,4 +425,8 @@
     return iter->second;
 }
 
+const sp<Looper> Choreographer::getLooper() {
+    return mLooper;
+}
+
 } // namespace android
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 4e12fd3..535a021 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -100,27 +100,31 @@
 int32_t LayerMetadata::getInt32(uint32_t key, int32_t fallback) const {
     if (!has(key)) return fallback;
     const std::vector<uint8_t>& data = mMap.at(key);
-    if (data.size() < sizeof(uint32_t)) return fallback;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt32();
+
+    // TODO: should handle when not equal?
+    if (data.size() < sizeof(int32_t)) return fallback;
+
+    int32_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 void LayerMetadata::setInt32(uint32_t key, int32_t value) {
     std::vector<uint8_t>& data = mMap[key];
-    Parcel p;
-    p.writeInt32(value);
-    data.resize(p.dataSize());
-    memcpy(data.data(), p.data(), p.dataSize());
+    data.resize(sizeof(value));
+    memcpy(data.data(), &value, sizeof(value));
 }
 
 std::optional<int64_t> LayerMetadata::getInt64(uint32_t key) const {
     if (!has(key)) return std::nullopt;
     const std::vector<uint8_t>& data = mMap.at(key);
+
+    // TODO: should handle when not equal?
     if (data.size() < sizeof(int64_t)) return std::nullopt;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt64();
+
+    int64_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 1e0aacd..0a28799 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -90,7 +90,6 @@
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
         isTrustedOverlay(false),
-        borderEnabled(false),
         bufferCrop(Rect::INVALID_RECT),
         destinationFrame(Rect::INVALID_RECT),
         dropInputMode(gui::DropInputMode::NONE) {
@@ -122,12 +121,6 @@
     SAFE_PARCEL(output.write, transparentRegion);
     SAFE_PARCEL(output.writeUint32, bufferTransform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
-    SAFE_PARCEL(output.writeBool, borderEnabled);
-    SAFE_PARCEL(output.writeFloat, borderWidth);
-    SAFE_PARCEL(output.writeFloat, borderColor.r);
-    SAFE_PARCEL(output.writeFloat, borderColor.g);
-    SAFE_PARCEL(output.writeFloat, borderColor.b);
-    SAFE_PARCEL(output.writeFloat, borderColor.a);
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dataspace));
     SAFE_PARCEL(output.write, hdrMetadata);
     SAFE_PARCEL(output.write, surfaceDamageRegion);
@@ -238,17 +231,6 @@
     SAFE_PARCEL(input.read, transparentRegion);
     SAFE_PARCEL(input.readUint32, &bufferTransform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
-    SAFE_PARCEL(input.readBool, &borderEnabled);
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderWidth = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.r = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.g = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.b = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.a = tmpFloat;
 
     uint32_t tmpUint32 = 0;
     SAFE_PARCEL(input.readUint32, &tmpUint32);
@@ -659,12 +641,6 @@
         what |= eShadowRadiusChanged;
         shadowRadius = other.shadowRadius;
     }
-    if (other.what & eRenderBorderChanged) {
-        what |= eRenderBorderChanged;
-        borderEnabled = other.borderEnabled;
-        borderWidth = other.borderWidth;
-        borderColor = other.borderColor;
-    }
     if (other.what & eDefaultFrameRateCompatibilityChanged) {
         what |= eDefaultFrameRateCompatibilityChanged;
         defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -794,7 +770,6 @@
     CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace);
     if (other.what & eMetadataChanged) diff |= eMetadataChanged;
     CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
-    CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
     CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
     CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 4f1356b..1d2ea3e 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2250,23 +2250,6 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder(
-        const sp<SurfaceControl>& sc, bool shouldEnable, float width, const half4& color) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-
-    s->what |= layer_state_t::eRenderBorderChanged;
-    s->borderEnabled = shouldEnable;
-    s->borderWidth = width;
-    s->borderColor = color;
-
-    registerSurfaceControlForCallback(sc);
-    return *this;
-}
-
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 86bf0ee..ad0d99d 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -73,14 +73,6 @@
     touchableRegion.orSelf(region);
 }
 
-bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
-    return touchableRegion.contains(x, y);
-}
-
-bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
-    return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
-}
-
 bool WindowInfo::supportsSplitTouch() const {
     return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
 }
diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index af138c7..13962fe 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -42,6 +42,17 @@
     }
 
     /**
+     * Refers to the time after which the idle screen's refresh rate is to be reduced
+     */
+    parcelable IdleScreenRefreshRateConfig {
+
+        /**
+         *  The timeout value in milli seconds
+         */
+        int timeoutMillis;
+    }
+
+    /**
      * Base mode ID. This is what system defaults to for all other settings, or
      * if the refresh rate range is not available.
      */
@@ -72,4 +83,13 @@
      * never smaller.
      */
     RefreshRateRanges appRequestRanges;
+
+    /**
+     * The config to represent the maximum time (in ms) for which the display can remain in an idle
+     * state before reducing the refresh rate to conserve power.
+     * Null value refers that the device is not configured to dynamically reduce the refresh rate
+     * based on external conditions.
+     * -1 refers to the current conditions requires no timeout
+     */
+    @nullable IdleScreenRefreshRateConfig idleScreenRefreshRateConfig;
 }
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index fc79b03..2e5aa4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -109,6 +109,7 @@
     virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
     int64_t getFrameInterval() const;
     bool inCallback() const;
+    const sp<Looper> getLooper();
 
 private:
     Choreographer(const Choreographer&) = delete;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 0fedea7..ca7acf9 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -179,7 +179,6 @@
         eCachingHintChanged = 0x00000200,
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
-        eRenderBorderChanged = 0x00001000,
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -258,8 +257,8 @@
             layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
-            layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged |
-            layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+            layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
+            layer_state_t::eStretchChanged;
 
     // Changes which invalidates the layer's visible region in CE.
     static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -388,11 +387,6 @@
     // should be trusted for input occlusion detection purposes
     bool isTrustedOverlay;
 
-    // Flag to indicate if border needs to be enabled on the layer
-    bool borderEnabled;
-    float borderWidth;
-    half4 borderColor;
-
     // Stretch effect to be applied to this layer
     StretchEffect stretchEffect;
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2888826..79224e6 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -744,9 +744,6 @@
                                          const Rect& destinationFrame);
         Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
 
-        Transaction& enableBorder(const sp<SurfaceControl>& sc, bool shouldEnable, float width,
-                                  const half4& color);
-
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 32d60be..2d1b51a 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -254,10 +254,6 @@
 
     void addTouchableRegion(const Rect& region);
 
-    bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
-
-    bool frameContainsPoint(int32_t x, int32_t y) const;
-
     bool supportsSplitTouch() const;
 
     bool isSpy() const;
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 1462c90..52acb51 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -44,28 +44,37 @@
 const bool DEBUG_TRANSPORT_CONSUMER =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
 
-void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
-    event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
-                     msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action,
-                     msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode,
-                     msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime,
-                     msg.body.key.eventTime);
+std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
+    std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
+    event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                      msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action,
+                      msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode,
+                      msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime,
+                      msg.body.key.eventTime);
+    return event;
 }
 
-void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
-    event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+std::unique_ptr<FocusEvent> createFocusEvent(const InputMessage& msg) {
+    std::unique_ptr<FocusEvent> event = std::make_unique<FocusEvent>();
+    event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+    return event;
 }
 
-void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
-    event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+std::unique_ptr<CaptureEvent> createCaptureEvent(const InputMessage& msg) {
+    std::unique_ptr<CaptureEvent> event = std::make_unique<CaptureEvent>();
+    event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+    return event;
 }
 
-void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
-    event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
-                     msg.body.drag.isExiting);
+std::unique_ptr<DragEvent> createDragEvent(const InputMessage& msg) {
+    std::unique_ptr<DragEvent> event = std::make_unique<DragEvent>();
+    event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                      msg.body.drag.isExiting);
+    return event;
 }
 
-void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
+std::unique_ptr<MotionEvent> createMotionEvent(const InputMessage& msg) {
+    std::unique_ptr<MotionEvent> event = std::make_unique<MotionEvent>();
     const uint32_t pointerCount = msg.body.motion.pointerCount;
     std::vector<PointerProperties> pointerProperties;
     pointerProperties.reserve(pointerCount);
@@ -83,15 +92,16 @@
     displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
                           msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
                           0, 0, 1});
-    event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
-                     msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action,
-                     msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags,
-                     msg.body.motion.metaState, msg.body.motion.buttonState,
-                     msg.body.motion.classification, transform, msg.body.motion.xPrecision,
-                     msg.body.motion.yPrecision, msg.body.motion.xCursorPosition,
-                     msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime,
-                     msg.body.motion.eventTime, pointerCount, pointerProperties.data(),
-                     pointerCoords.data());
+    event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                      msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action,
+                      msg.body.motion.actionButton, msg.body.motion.flags,
+                      msg.body.motion.edgeFlags, msg.body.motion.metaState,
+                      msg.body.motion.buttonState, msg.body.motion.classification, transform,
+                      msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+                      msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+                      displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+                      pointerCount, pointerProperties.data(), pointerCoords.data());
+    return event;
 }
 
 void addSample(MotionEvent& event, const InputMessage& msg) {
@@ -107,8 +117,10 @@
     event.addSample(msg.body.motion.eventTime, pointerCoords.data());
 }
 
-void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
-    event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
+    std::unique_ptr<TouchModeEvent> event = std::make_unique<TouchModeEvent>();
+    event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+    return event;
 }
 
 std::string outboundMessageToString(const InputMessage& outboundMsg) {
@@ -388,15 +400,13 @@
 void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
     switch (msg.header.type) {
         case InputMessage::Type::KEY: {
-            KeyEvent keyEvent;
-            initializeKeyEvent(keyEvent, msg);
+            std::unique_ptr<KeyEvent> keyEvent = createKeyEvent(msg);
             mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
             break;
         }
 
         case InputMessage::Type::MOTION: {
-            MotionEvent motionEvent;
-            initializeMotionEvent(motionEvent, msg);
+            std::unique_ptr<MotionEvent> motionEvent = createMotionEvent(msg);
             mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
             break;
         }
@@ -411,29 +421,25 @@
         }
 
         case InputMessage::Type::FOCUS: {
-            FocusEvent focusEvent;
-            initializeFocusEvent(focusEvent, msg);
+            std::unique_ptr<FocusEvent> focusEvent = createFocusEvent(msg);
             mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
             break;
         }
 
         case InputMessage::Type::CAPTURE: {
-            CaptureEvent captureEvent;
-            initializeCaptureEvent(captureEvent, msg);
+            std::unique_ptr<CaptureEvent> captureEvent = createCaptureEvent(msg);
             mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
             break;
         }
 
         case InputMessage::Type::DRAG: {
-            DragEvent dragEvent;
-            initializeDragEvent(dragEvent, msg);
+            std::unique_ptr<DragEvent> dragEvent = createDragEvent(msg);
             mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
             break;
         }
 
         case InputMessage::Type::TOUCH_MODE: {
-            TouchModeEvent touchModeEvent;
-            initializeTouchModeEvent(touchModeEvent, msg);
+            std::unique_ptr<TouchModeEvent> touchModeEvent = createTouchModeEvent(msg);
             mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
             break;
         }
@@ -448,7 +454,7 @@
     const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
     bool producedEvents = false;
     for (auto& [deviceId, messages] : mBatches) {
-        MotionEvent motion;
+        std::unique_ptr<MotionEvent> motion;
         std::optional<uint32_t> firstSeqForBatch;
         std::vector<uint32_t> sequences;
         while (!messages.empty()) {
@@ -456,20 +462,21 @@
             if (msg.body.motion.eventTime > frameTime) {
                 break;
             }
-            if (!firstSeqForBatch.has_value()) {
-                initializeMotionEvent(motion, msg);
+            if (motion == nullptr) {
+                motion = createMotionEvent(msg);
                 firstSeqForBatch = msg.header.seq;
                 const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
                 if (!inserted) {
                     LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
                 }
             } else {
-                addSample(motion, msg);
+                addSample(*motion, msg);
                 mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
             }
             messages.pop();
         }
-        if (firstSeqForBatch.has_value()) {
+        if (motion != nullptr) {
+            LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
             mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
             producedEvents = true;
         } else {
@@ -520,9 +527,8 @@
             std::queue<InputMessage> tmpQueue = messages;
             while (!tmpQueue.empty()) {
                 LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
-                MotionEvent motion;
-                initializeMotionEvent(motion, tmpQueue.front());
-                out += std::string("    ") + streamableToString(motion) + "\n";
+                std::unique_ptr<MotionEvent> motion = createMotionEvent(tmpQueue.front());
+                out += std::string("    ") + streamableToString(*motion) + "\n";
                 tmpQueue.pop();
             }
         }
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c4e3ff6..e836a4c 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -22,17 +22,21 @@
 #include <cmath>
 #include <cstddef>
 #include <cstdint>
+#include <limits>
 #include <string>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <android/input.h>
+#include <com_android_input_flags.h>
 
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
 #include <input/TfLiteMotionPredictor.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 namespace {
 
@@ -197,7 +201,14 @@
             // device starts to speed up, but avoids producing noisy predictions as it slows down.
             break;
         }
-        // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+        if (input_flags::enable_jerk_prediction_pruning()) {
+            // TODO(b/266747654): Stop predictions if confidence is < some threshold
+            // Arbitrarily high pruning index, will correct once jerk thresholding is implemented.
+            const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max();
+            if (i > upperBoundPredictionIndex) {
+                break;
+            }
+        }
 
         const TfLiteMotionPredictorSample::Point predictedPoint =
                 convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index b48b0fb..c1f5e2a 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -128,3 +128,10 @@
   description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
   bug: "281106755"
 }
+
+flag {
+  name: "enable_jerk_prediction_pruning"
+  namespace: "input"
+  description: "Enable prediction pruning based on jerk thresholds."
+  bug: "266747654"
+}
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index e24ae49..6593497 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -341,19 +341,19 @@
     // The sequence number to use when publishing the next event
     uint32_t mSeq = 1;
 
-    BlockingQueue<KeyEvent> mKeyEvents;
-    BlockingQueue<MotionEvent> mMotionEvents;
-    BlockingQueue<FocusEvent> mFocusEvents;
-    BlockingQueue<CaptureEvent> mCaptureEvents;
-    BlockingQueue<DragEvent> mDragEvents;
-    BlockingQueue<TouchModeEvent> mTouchModeEvents;
+    BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+    BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
 
     // InputConsumerCallbacks interface
-    void onKeyEvent(KeyEvent&& event, uint32_t seq) override {
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
         mKeyEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     }
-    void onMotionEvent(MotionEvent&& event, uint32_t seq) override {
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
         mMotionEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     }
@@ -363,19 +363,19 @@
         }
         mConsumer->consumeBatchedInputEvents(std::nullopt);
     };
-    void onFocusEvent(FocusEvent&& event, uint32_t seq) override {
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
         mFocusEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     };
-    void onCaptureEvent(CaptureEvent&& event, uint32_t seq) override {
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
         mCaptureEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     };
-    void onDragEvent(DragEvent&& event, uint32_t seq) override {
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
         mDragEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     }
-    void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) override {
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
         mTouchModeEvents.push(std::move(event));
         mConsumer->finishInputEvent(seq, true);
     };
@@ -465,15 +465,15 @@
                                          eventTime);
     ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
 
-    std::optional<KeyEvent> keyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+    std::optional<std::unique_ptr<KeyEvent>> optKeyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optKeyEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<KeyEvent> keyEvent = std::move(*optKeyEvent);
 
     sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT);
     std::optional<bool> probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT);
     ASSERT_TRUE(probablyHasInput.has_value());
     ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed";
 
-    ASSERT_TRUE(keyEvent.has_value()) << "consumer should have returned non-NULL event";
-
     EXPECT_EQ(eventId, keyEvent->getId());
     EXPECT_EQ(deviceId, keyEvent->getDeviceId());
     EXPECT_EQ(source, keyEvent->getSource());
@@ -540,7 +540,8 @@
     publishMotionEvent(*mPublisher, args);
 
     // Ensure no event arrives because the UI thread is blocked
-    std::optional<MotionEvent> noEvent = mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
+    std::optional<std::unique_ptr<MotionEvent>> noEvent =
+            mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
     ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent;
 
     Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
@@ -559,8 +560,9 @@
     }
     mNotifyLooperMayProceed.notify_all();
 
-    std::optional<MotionEvent> motion = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(motion.has_value());
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
     ASSERT_EQ(ACTION_MOVE, motion->getAction());
 
     verifyFinishedSignal(*mPublisher, seq, publishTime);
@@ -573,8 +575,9 @@
     nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
     publishMotionEvent(*mPublisher, args);
 
-    std::optional<MotionEvent> event = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> event = std::move(*optMotion);
 
     verifyArgsEqualToEvent(args, *event);
 
@@ -592,8 +595,9 @@
     status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
     ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
 
-    std::optional<FocusEvent> focusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(focusEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::optional<std::unique_ptr<FocusEvent>> optFocusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optFocusEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<FocusEvent> focusEvent = std::move(*optFocusEvent);
     EXPECT_EQ(eventId, focusEvent->getId());
     EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
 
@@ -611,9 +615,9 @@
     status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled);
     ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK";
 
-    std::optional<CaptureEvent> event = mCaptureEvents.popWithTimeout(TIMEOUT);
-
-    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+    std::optional<std::unique_ptr<CaptureEvent>> optEvent = mCaptureEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<CaptureEvent> event = std::move(*optEvent);
 
     const CaptureEvent& captureEvent = *event;
     EXPECT_EQ(eventId, captureEvent.getId());
@@ -635,9 +639,9 @@
     status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting);
     ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK";
 
-    std::optional<DragEvent> event = mDragEvents.popWithTimeout(TIMEOUT);
-
-    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+    std::optional<std::unique_ptr<DragEvent>> optEvent = mDragEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<DragEvent> event = std::move(*optEvent);
 
     const DragEvent& dragEvent = *event;
     EXPECT_EQ(eventId, dragEvent.getId());
@@ -659,8 +663,10 @@
     status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
     ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
 
-    std::optional<TouchModeEvent> event = mTouchModeEvents.popWithTimeout(TIMEOUT);
-    ASSERT_NE(std::nullopt, event);
+    std::optional<std::unique_ptr<TouchModeEvent>> optEvent =
+            mTouchModeEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value());
+    std::unique_ptr<TouchModeEvent> event = std::move(*optEvent);
 
     const TouchModeEvent& touchModeEvent = *event;
     EXPECT_EQ(eventId, touchModeEvent.getId());
diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h
deleted file mode 100644
index 0ee6661..0000000
--- a/libs/renderengine/include/renderengine/BorderRenderInfo.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-#include <math/mat4.h>
-#include <ui/Region.h>
-
-namespace android {
-namespace renderengine {
-
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    Region combinedRegion;
-
-    bool operator==(const BorderRenderInfo& rhs) const {
-        return (width == rhs.width && color == rhs.color &&
-                combinedRegion.hasSameRects(rhs.combinedRegion));
-    }
-};
-
-} // namespace renderengine
-} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 8d7c13c..deb6253 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -22,7 +22,6 @@
 
 #include <math/mat4.h>
 #include <renderengine/PrintMatrix.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/DisplayId.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
@@ -87,8 +86,6 @@
     // Configures the rendering intent of the output display. This is used for tonemapping.
     aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
             aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
-
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
@@ -100,8 +97,7 @@
             lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform &&
             lhs.orientation == rhs.orientation &&
             lhs.targetLuminanceNits == rhs.targetLuminanceNits &&
-            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent &&
-            lhs.borderInfoList == rhs.borderInfoList;
+            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent;
 }
 
 static const char* orientation_to_string(uint32_t orientation) {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 1d8e6f1..2484650 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -1131,25 +1131,6 @@
             skgpu::ganesh::Flush(activeSurface);
         }
     }
-    for (const auto& borderRenderInfo : display.borderInfoList) {
-        SkPaint p;
-        p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
-                             borderRenderInfo.color.b, borderRenderInfo.color.a});
-        p.setAntiAlias(true);
-        p.setStyle(SkPaint::kStroke_Style);
-        p.setStrokeWidth(borderRenderInfo.width);
-        SkRegion sk_region;
-        SkPath path;
-
-        // Construct a final SkRegion using Regions
-        for (const auto& r : borderRenderInfo.combinedRegion) {
-            sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
-        }
-
-        sk_region.getBoundaryPath(&path);
-        canvas->drawPath(path, p);
-        path.close();
-    }
 
     surfaceAutoSaveRestore.restore();
     mCapture->endCapture();
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 7b8eb84..3a07205 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -2490,51 +2490,6 @@
     expectBufferColor(rect, 0, 128, 0, 128);
 }
 
-TEST_P(RenderEngineTest, testBorder) {
-    if (!GetParam()->apiSupported()) {
-        GTEST_SKIP();
-    }
-
-    initializeRenderEngine();
-
-    const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
-
-    const auto displayRect = Rect(1080, 2280);
-    renderengine::DisplaySettings display{
-            .physicalDisplay = displayRect,
-            .clip = displayRect,
-            .outputDataspace = dataspace,
-    };
-    display.borderInfoList.clear();
-    renderengine::BorderRenderInfo info;
-    info.combinedRegion = Region(Rect(99, 99, 199, 199));
-    info.width = 20.0f;
-    info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f};
-    display.borderInfoList.emplace_back(info);
-
-    const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
-    const renderengine::LayerSettings greenLayer{
-            .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
-            .source =
-                    renderengine::PixelSource{
-                            .buffer =
-                                    renderengine::Buffer{
-                                            .buffer = greenBuffer,
-                                            .usePremultipliedAlpha = true,
-                                    },
-                    },
-            .alpha = 1.0f,
-            .sourceDataspace = dataspace,
-            .whitePointNits = 200.f,
-    };
-
-    std::vector<renderengine::LayerSettings> layers;
-    layers.emplace_back(greenLayer);
-    invokeDraw(display, layers);
-
-    expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1);
-}
-
 TEST_P(RenderEngineTest, testDimming) {
     if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
diff --git a/libs/sensor/OWNERS b/libs/sensor/OWNERS
index 90c2330..7347ac7 100644
--- a/libs/sensor/OWNERS
+++ b/libs/sensor/OWNERS
@@ -1,3 +1 @@
-arthuri@google.com
 bduddie@google.com
-stange@google.com
diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
index 4e3c83f..2c1c2a4 100644
--- a/libs/tracing_perfetto/include/tracing_perfetto.h
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -46,7 +46,7 @@
 
 Result traceCounter(uint64_t category, const char* name, int64_t value);
 
-uint64_t getEnabledCategories();
+bool isTagEnabled(uint64_t category);
 
 }  // namespace tracing_perfetto
 
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
index 19d1eb6..6f716ee 100644
--- a/libs/tracing_perfetto/tracing_perfetto.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -130,12 +130,13 @@
   }
 }
 
-uint64_t getEnabledCategories() {
-  if (internal::isPerfettoRegistered()) {
-    // TODO(b/303199244): Return only enabled categories and not all registered ones
-    return internal::getDefaultCategories();
+bool isTagEnabled(uint64_t category) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  if (perfettoTeCategory != nullptr) {
+    return true;
   } else {
-    return atrace_get_enabled_tags();
+    return (atrace_get_enabled_tags() & category) != 0;
   }
 }
 
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 11064ae..f14a5cf 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -23,6 +23,7 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <android/llndk-versioning.h>
 #include <binder/IPCThreadState.h>
 #include <dlfcn.h>
 #include <ui/FatVector.h>
@@ -91,8 +92,7 @@
 
         void* so = nullptr;
         // TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
-        // TODO(b/302113279) use __ANDROID_VENDOR_API__ for vendor variant
-        if (__builtin_available(android __ANDROID_API_FUTURE__, *)) {
+        if API_LEVEL_AT_LEAST(__ANDROID_API_FUTURE__, 202404) {
             so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
                                                             RTLD_LOCAL | RTLD_NOW);
         } else {
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index c333814..1aa1077 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -37,6 +37,11 @@
             args.pointerProperties[0].toolType == ToolType::FINGER;
 }
 
+bool isFromDrawingTablet(const NotifyMotionArgs& args) {
+    return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
+            isStylusToolType(args.pointerProperties[0].toolType);
+}
+
 bool isHoverAction(int32_t action) {
     return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
             action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
@@ -46,6 +51,13 @@
     return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
 }
 
+bool isMouseOrTouchpad(uint32_t sources) {
+    // Check if this is a mouse or touchpad, but not a drawing tablet.
+    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
 inline void notifyPointerDisplayChange(std::optional<std::tuple<int32_t, FloatPoint>> change,
                                        PointerChoreographerPolicyInterface& policy) {
     if (!change) {
@@ -55,6 +67,18 @@
     policy.notifyPointerDisplayIdChanged(displayId, cursorPosition);
 }
 
+void setIconForController(const std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle>& icon,
+                          PointerControllerInterface& controller) {
+    if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+        if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+            LOG(FATAL) << "SpriteIcon should not be null";
+        }
+        controller.setCustomPointerIcon(*std::get<std::unique_ptr<SpriteIcon>>(icon));
+    } else {
+        controller.updatePointerIcon(std::get<PointerIconStyle>(icon));
+    }
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
@@ -107,6 +131,8 @@
         return processMouseEventLocked(args);
     } else if (isFromTouchpad(args)) {
         return processTouchpadEventLocked(args);
+    } else if (isFromDrawingTablet(args)) {
+        processDrawingTabletEventLocked(args);
     } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
         processStylusHoverEventLocked(args);
     } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
@@ -189,6 +215,36 @@
     return newArgs;
 }
 
+void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
+    if (args.displayId == ADISPLAY_ID_NONE) {
+        return;
+    }
+
+    if (args.getPointerCount() != 1) {
+        LOG(WARNING) << "Only drawing tablet events with a single pointer are currently supported: "
+                     << args.dump();
+    }
+
+    // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist.
+    auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
+                                                              getMouseControllerConstructor(
+                                                                      args.displayId));
+
+    PointerControllerInterface& pc = *it->second;
+
+    const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+    const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+    pc.setPosition(x, y);
+    if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+        //   immediately by a DOWN event.
+        pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+    } else if (canUnfadeOnDisplay(args.displayId)) {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
+}
+
 /**
  * When screen is touched, fade the mouse pointer on that display. We only call fade for
  * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
@@ -255,6 +311,8 @@
     const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
     pc.setPosition(x, y);
     if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+        //   immediately by a DOWN event.
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
         pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
     } else if (canUnfadeOnDisplay(args.displayId)) {
@@ -284,6 +342,7 @@
     std::scoped_lock _l(mLock);
     mTouchPointersByDevice.erase(args.deviceId);
     mStylusPointersByDevice.erase(args.deviceId);
+    mDrawingTabletPointersByDevice.erase(args.deviceId);
 }
 
 void PointerChoreographer::notifyPointerCaptureChanged(
@@ -320,6 +379,11 @@
         std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
         dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
     }
+    dump += INDENT "DrawingTabletControllers:\n";
+    for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(drawingTabletController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
     dump += "\n";
 }
 
@@ -361,13 +425,13 @@
     std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
     std::set<DeviceId> touchDevicesToKeep;
     std::set<DeviceId> stylusDevicesToKeep;
+    std::set<DeviceId> drawingTabletDevicesToKeep;
 
     // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
     // new PointerControllers if necessary.
     for (const auto& info : mInputDeviceInfos) {
         const uint32_t sources = info.getSources();
-        if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
-            isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
+        if (isMouseOrTouchpad(sources)) {
             const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
             mouseDisplaysToKeep.insert(displayId);
             // For mice, show the cursor immediately when the device is first connected or
@@ -388,6 +452,10 @@
             info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
             stylusDevicesToKeep.insert(info.getId());
         }
+        if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) &&
+            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            drawingTabletDevicesToKeep.insert(info.getId());
+        }
     }
 
     // Remove PointerControllers no longer needed.
@@ -400,6 +468,9 @@
     std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
         return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
     });
+    std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) {
+        return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end();
+    });
     std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
         return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
                             [id](const auto& info) { return info.getId() == id; }) ==
@@ -460,6 +531,12 @@
                     stylusPointerController->setDisplayViewport(viewport);
                 }
             }
+            for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+                const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+                if (info && info->getAssociatedDisplayId() == displayId) {
+                    drawingTabletController->setDisplayViewport(viewport);
+                }
+            }
         }
         mViewports = viewports;
         pointerDisplayChange = calculatePointerDisplayChangeToNotify();
@@ -533,42 +610,35 @@
         return false;
     }
     const uint32_t sources = info->getSources();
-    const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
 
-    if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
-        stylusPointerIt != mStylusPointersByDevice.end()) {
-        if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
-            if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
-                LOG(FATAL) << "SpriteIcon should not be null";
-            }
-            stylusPointerIt->second->setCustomPointerIcon(
-                    *std::get<std::unique_ptr<SpriteIcon>>(icon));
-        } else {
-            stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+    if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE)) {
+        auto it = mDrawingTabletPointersByDevice.find(deviceId);
+        if (it != mDrawingTabletPointersByDevice.end()) {
+            setIconForController(icon, *it->second);
+            return true;
         }
-    } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
-        if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
-            mousePointerIt != mMousePointersByDisplay.end()) {
-            if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
-                if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
-                    LOG(FATAL) << "SpriteIcon should not be null";
-                }
-                mousePointerIt->second->setCustomPointerIcon(
-                        *std::get<std::unique_ptr<SpriteIcon>>(icon));
-            } else {
-                mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
-            }
+    }
+    if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
+        auto it = mStylusPointersByDevice.find(deviceId);
+        if (it != mStylusPointersByDevice.end()) {
+            setIconForController(icon, *it->second);
+            return true;
+        }
+    }
+    if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+        auto it = mMousePointersByDisplay.find(displayId);
+        if (it != mMousePointersByDisplay.end()) {
+            setIconForController(icon, *it->second);
+            return true;
         } else {
             LOG(WARNING) << "No mouse pointer controller found for display " << displayId
                          << ", device " << deviceId << ".";
             return false;
         }
-    } else {
-        LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
-                     << deviceId << ".";
-        return false;
     }
-    return true;
+    LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " << deviceId
+                 << ".";
+    return false;
 }
 
 void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) {
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index db1488b..a3c210e 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -123,6 +123,7 @@
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processDeviceReset(const NotifyDeviceResetArgs& args);
@@ -144,6 +145,8 @@
             GUARDED_BY(mLock);
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
             GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mDrawingTabletPointersByDevice
+            GUARDED_BY(mLock);
 
     int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
     int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index dc220fe..2df86a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -88,12 +88,13 @@
 }
 
 // Create the input tracing backend that writes to perfetto from a single thread.
-std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled() {
+std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled(
+        trace::impl::PerfettoBackend::GetPackageUid getPackageUid) {
     if (!isInputTracingEnabled()) {
         return nullptr;
     }
     return std::make_unique<trace::impl::ThreadedBackend<trace::impl::PerfettoBackend>>(
-            trace::impl::PerfettoBackend());
+            trace::impl::PerfettoBackend(getPackageUid));
 }
 
 template <class Entry>
@@ -598,6 +599,18 @@
     return true;
 }
 
+// Returns true if the given window's frame can occlude pointer events at the given display
+// location.
+bool windowOccludesTouchAt(const WindowInfo& windowInfo, int displayId, float x, float y,
+                           const ui::Transform& displayTransform) {
+    if (windowInfo.displayId != displayId) {
+        return false;
+    }
+    const auto frame = displayTransform.transform(windowInfo.frame);
+    const auto p = floor(displayTransform.transform(x, y));
+    return p.x >= frame.left && p.x < frame.right && p.y >= frame.top && p.y < frame.bottom;
+}
+
 bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
     return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
             isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
@@ -891,7 +904,9 @@
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
-      : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {}
+      : InputDispatcher(policy, createInputTracingBackendIfEnabled([&policy](std::string pkg) {
+                            return policy.getPackageUid(pkg);
+                        })) {}
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
@@ -3105,7 +3120,7 @@
  * If neither of those is true, then it means the touch can be allowed.
  */
 InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
-        const sp<WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const {
+        const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     const WindowInfo* windowInfo = windowHandle->getInfo();
     int32_t displayId = windowInfo->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
@@ -3119,7 +3134,8 @@
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
-        if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) &&
+        if (canBeObscuredBy(windowHandle, otherHandle) &&
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) &&
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
@@ -3189,7 +3205,7 @@
 }
 
 bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
-                                                    int32_t x, int32_t y) const {
+                                                    float x, float y) const {
     int32_t displayId = windowHandle->getInfo()->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
@@ -3198,7 +3214,7 @@
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            otherInfo->frameContainsPoint(x, y)) {
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
             return true;
         }
     }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 13571b3..edca63e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -566,11 +566,11 @@
     };
 
     TouchOcclusionInfo computeTouchOcclusionInfoLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const
+            const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const
             REQUIRES(mLock);
     bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
     bool isWindowObscuredAtPointLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                       int32_t x, int32_t y) const REQUIRES(mLock);
+                                       float x, float y) const REQUIRES(mLock);
     bool isWindowObscuredLocked(const sp<android::gui::WindowInfoHandle>& windowHandle) const
             REQUIRES(mLock);
     std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info,
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 62c2b02..91a3e3f 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -163,6 +163,9 @@
     virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                          const std::set<gui::Uid>& uids) = 0;
 
+    /* Get the UID associated with the given package. */
+    virtual gui::Uid getPackageUid(std::string package) = 0;
+
 private:
     // Additional key latency in case a connection is still processing some motion events.
     // This will help with the case when a user touched a button that opens a new window,
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index cee741c..c431fb7 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -21,8 +21,26 @@
 
 namespace android::inputdispatcher::trace {
 
+namespace {
+
+using namespace ftl::flag_operators;
+
+// The trace config to use for maximal tracing.
+const impl::TraceConfig CONFIG_TRACE_ALL{
+        .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
+                impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
+        .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
+                                  .matchAllPackages = {},
+                                  .matchAnyPackages = {},
+                                  .matchSecure{},
+                                  .matchImeConnectionActive = {}}},
+};
+
+} // namespace
+
 void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
-                                                         proto::AndroidMotionEvent& outProto) {
+                                                         proto::AndroidMotionEvent& outProto,
+                                                         bool isRedacted) {
     outProto.set_event_id(event.id);
     outProto.set_event_time_nanos(event.eventTime);
     outProto.set_down_time_nanos(event.downTime);
@@ -31,11 +49,15 @@
     outProto.set_device_id(event.deviceId);
     outProto.set_display_id(event.displayId);
     outProto.set_classification(static_cast<int32_t>(event.classification));
-    outProto.set_cursor_position_x(event.xCursorPosition);
-    outProto.set_cursor_position_y(event.yCursorPosition);
     outProto.set_flags(event.flags);
     outProto.set_policy_flags(event.policyFlags);
 
+    if (!isRedacted) {
+        outProto.set_cursor_position_x(event.xCursorPosition);
+        outProto.set_cursor_position_y(event.yCursorPosition);
+        outProto.set_meta_state(event.metaState);
+    }
+
     for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
         auto* pointer = outProto.add_pointer();
 
@@ -49,13 +71,17 @@
             const auto axis = bits.clearFirstMarkedBit();
             auto axisEntry = pointer->add_axis_value();
             axisEntry->set_axis(axis);
-            axisEntry->set_value(coords.values[axisIndex]);
+
+            if (!isRedacted) {
+                axisEntry->set_value(coords.values[axisIndex]);
+            }
         }
     }
 }
 
 void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
-                                                      proto::AndroidKeyEvent& outProto) {
+                                                      proto::AndroidKeyEvent& outProto,
+                                                      bool isRedacted) {
     outProto.set_event_id(event.id);
     outProto.set_event_time_nanos(event.eventTime);
     outProto.set_down_time_nanos(event.downTime);
@@ -63,21 +89,28 @@
     outProto.set_action(event.action);
     outProto.set_device_id(event.deviceId);
     outProto.set_display_id(event.displayId);
-    outProto.set_key_code(event.keyCode);
-    outProto.set_scan_code(event.scanCode);
-    outProto.set_meta_state(event.metaState);
     outProto.set_repeat_count(event.repeatCount);
     outProto.set_flags(event.flags);
     outProto.set_policy_flags(event.policyFlags);
+
+    if (!isRedacted) {
+        outProto.set_key_code(event.keyCode);
+        outProto.set_scan_code(event.scanCode);
+        outProto.set_meta_state(event.metaState);
+    }
 }
 
 void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
-        const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto) {
+        const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto,
+        bool isRedacted) {
     std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
     outProto.set_vsync_id(args.vsyncId);
     outProto.set_window_id(args.windowId);
     outProto.set_resolved_flags(args.resolvedFlags);
 
+    if (isRedacted) {
+        return;
+    }
     if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
         for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
             auto* pointerProto = outProto.add_dispatched_pointer();
@@ -105,4 +138,65 @@
     }
 }
 
+impl::TraceConfig AndroidInputEventProtoConverter::parseConfig(
+        proto::AndroidInputEventConfig::Decoder& protoConfig) {
+    if (protoConfig.has_mode() &&
+        protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
+        // User has requested the preset for maximal tracing
+        return CONFIG_TRACE_ALL;
+    }
+
+    impl::TraceConfig config;
+
+    // Parse trace flags
+    if (protoConfig.has_trace_dispatcher_input_events() &&
+        protoConfig.trace_dispatcher_input_events()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
+    }
+    if (protoConfig.has_trace_dispatcher_window_dispatch() &&
+        protoConfig.trace_dispatcher_window_dispatch()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
+    }
+
+    // Parse trace rules
+    auto rulesIt = protoConfig.rules();
+    while (rulesIt) {
+        proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
+        config.rules.emplace_back();
+        auto& rule = config.rules.back();
+
+        rule.level = protoRule.has_trace_level()
+                ? static_cast<impl::TraceLevel>(protoRule.trace_level())
+                : impl::TraceLevel::TRACE_LEVEL_NONE;
+
+        if (protoRule.has_match_all_packages()) {
+            auto pkgIt = protoRule.match_all_packages();
+            while (pkgIt) {
+                rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_any_packages()) {
+            auto pkgIt = protoRule.match_any_packages();
+            while (pkgIt) {
+                rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_secure()) {
+            rule.matchSecure = protoRule.match_secure();
+        }
+
+        if (protoRule.has_match_ime_connection_active()) {
+            rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
+        }
+
+        rulesIt++;
+    }
+
+    return config;
+}
+
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
index ab5f9ca..887913f 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -16,9 +16,11 @@
 
 #pragma once
 
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
 
 #include "InputTracingBackendInterface.h"
+#include "InputTracingPerfettoBackendConfig.h"
 
 namespace proto = perfetto::protos::pbzero;
 
@@ -30,10 +32,14 @@
 class AndroidInputEventProtoConverter {
 public:
     static void toProtoMotionEvent(const TracedMotionEvent& event,
-                                   proto::AndroidMotionEvent& outProto);
-    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto);
+                                   proto::AndroidMotionEvent& outProto, bool isRedacted);
+    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto,
+                                bool isRedacted);
     static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
-                                           proto::AndroidWindowInputDispatchEvent& outProto);
+                                           proto::AndroidWindowInputDispatchEvent& outProto,
+                                           bool isRedacted);
+
+    static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig);
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 47e27be..55ed5c6 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -19,6 +19,7 @@
 #include "InputTracer.h"
 
 #include <android-base/logging.h>
+#include <private/android_filesystem_config.h>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -30,7 +31,7 @@
     using V::operator()...;
 };
 
-TracedEvent createTracedEvent(const MotionEntry& e) {
+TracedEvent createTracedEvent(const MotionEntry& e, EventType type) {
     return TracedMotionEvent{e.id,
                              e.eventTime,
                              e.policyFlags,
@@ -50,13 +51,14 @@
                              e.yCursorPosition,
                              e.downTime,
                              e.pointerProperties,
-                             e.pointerCoords};
+                             e.pointerCoords,
+                             type};
 }
 
-TracedEvent createTracedEvent(const KeyEntry& e) {
+TracedEvent createTracedEvent(const KeyEntry& e, EventType type) {
     return TracedKeyEvent{e.id,        e.eventTime, e.policyFlags, e.deviceId, e.source,
                           e.displayId, e.action,    e.keyCode,     e.scanCode, e.metaState,
-                          e.downTime,  e.flags,     e.repeatCount};
+                          e.downTime,  e.flags,     e.repeatCount, type};
 }
 
 void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args,
@@ -70,6 +72,24 @@
     return std::visit([](const auto& event) { return event.id; }, v);
 }
 
+// Helper class to extract relevant information from InputTarget.
+struct InputTargetInfo {
+    gui::Uid uid;
+    bool isSecureWindow;
+};
+
+InputTargetInfo getTargetInfo(const InputTarget& target) {
+    if (target.windowHandle == nullptr) {
+        if (!target.connection->monitor) {
+            LOG(FATAL) << __func__ << ": Window is not set for non-monitor target";
+        }
+        // This is a global monitor, assume its target is the system.
+        return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
+    }
+    return {target.windowHandle->getInfo()->ownerUid,
+            target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)};
+}
+
 } // namespace
 
 // --- InputTracer ---
@@ -83,10 +103,10 @@
 
     if (entry.type == EventEntry::Type::MOTION) {
         const auto& motion = static_cast<const MotionEntry&>(entry);
-        eventState->events.emplace_back(createTracedEvent(motion));
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::INBOUND));
     } else if (entry.type == EventEntry::Type::KEY) {
         const auto& key = static_cast<const KeyEntry&>(entry);
-        eventState->events.emplace_back(createTracedEvent(key));
+        eventState->events.emplace_back(createTracedEvent(key, EventType::INBOUND));
     } else {
         LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
     }
@@ -103,19 +123,25 @@
 void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
                                        const InputTarget& target) {
     auto& eventState = getState(cookie);
+    const InputTargetInfo& targetInfo = getTargetInfo(target);
     if (eventState->isEventProcessingComplete) {
-        // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called.
+        // Disallow adding new targets after eventProcessingComplete() is called.
+        if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+            LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete";
+        }
         return;
     }
     if (isDerivedCookie(cookie)) {
-        // TODO(b/210460522): Disallow adding new targets from a derived cookie.
+        // Disallow adding new targets from a derived cookie.
+        if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+            LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie";
+        }
         return;
     }
-    if (target.windowHandle != nullptr) {
-        eventState->isSecure |= target.windowHandle->getInfo()->layoutParamsFlags.test(
-                gui::WindowInfo::Flag::SECURE);
-        // TODO(b/210460522): Set events as sensitive when the IME connection is active.
-    }
+
+    eventState->targets.emplace(targetInfo.uid);
+    eventState->isSecure |= targetInfo.isSecureWindow;
+    // TODO(b/210460522): Set events as sensitive when the IME connection is active.
 }
 
 void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
@@ -138,10 +164,10 @@
 
     if (entry.type == EventEntry::Type::MOTION) {
         const auto& motion = static_cast<const MotionEntry&>(entry);
-        eventState->events.emplace_back(createTracedEvent(motion));
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::SYNTHESIZED));
     } else if (entry.type == EventEntry::Type::KEY) {
         const auto& key = static_cast<const KeyEntry&>(entry);
-        eventState->events.emplace_back(createTracedEvent(key));
+        eventState->events.emplace_back(createTracedEvent(key, EventType::SYNTHESIZED));
     } else {
         LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
     }
@@ -150,8 +176,10 @@
         // It is possible for a derived event to be dispatched some time after the original event
         // is dispatched, such as in the case of key fallback events. To account for these cases,
         // derived events can be traced after the processing is complete for the original event.
-        const TracedEventArgs traceArgs{.isSecure = eventState->isSecure};
-        writeEventToBackend(eventState->events.back(), traceArgs, *mBackend);
+        const auto& event = eventState->events.back();
+        const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
+                                        .targets = eventState->targets};
+        writeEventToBackend(event, std::move(traceArgs), *mBackend);
     }
     return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
 }
@@ -160,38 +188,34 @@
                                      const EventTrackerInterface& cookie) {
     auto& eventState = getState(cookie);
     const EventEntry& entry = *dispatchEntry.eventEntry;
+    const int32_t eventId = entry.id;
     // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable.
     // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced,
     // so we need to find the repeatCount at the time of dispatching to trace it accurately.
     int32_t resolvedKeyRepeatCount = 0;
-
-    TracedEvent traced;
-    if (entry.type == EventEntry::Type::MOTION) {
-        const auto& motion = static_cast<const MotionEntry&>(entry);
-        traced = createTracedEvent(motion);
-    } else if (entry.type == EventEntry::Type::KEY) {
-        const auto& key = static_cast<const KeyEntry&>(entry);
-        resolvedKeyRepeatCount = key.repeatCount;
-        traced = createTracedEvent(key);
-    } else {
-        LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+    if (entry.type == EventEntry::Type::KEY) {
+        resolvedKeyRepeatCount = static_cast<const KeyEntry&>(entry).repeatCount;
     }
 
     auto tracedEventIt =
             std::find_if(eventState->events.begin(), eventState->events.end(),
-                         [&traced](const auto& event) { return getId(traced) == getId(event); });
+                         [eventId](const auto& event) { return eventId == getId(event); });
     if (tracedEventIt == eventState->events.end()) {
         LOG(FATAL)
                 << __func__
                 << ": Failed to find a previously traced event that matches the dispatched event";
     }
 
+    if (eventState->targets.count(dispatchEntry.targetUid) == 0) {
+        LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting";
+    }
+
     // The vsyncId only has meaning if the event is targeting a window.
     const int32_t windowId = dispatchEntry.windowId.value_or(0);
     const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
 
     // TODO(b/210460522): Pass HMAC into traceEventDispatch.
-    const WindowDispatchArgs windowDispatchArgs{std::move(traced),
+    const WindowDispatchArgs windowDispatchArgs{*tracedEventIt,
                                                 dispatchEntry.deliveryTime,
                                                 dispatchEntry.resolvedFlags,
                                                 dispatchEntry.targetUid,
@@ -202,8 +226,9 @@
                                                 /*hmac=*/{},
                                                 resolvedKeyRepeatCount};
     if (eventState->isEventProcessingComplete) {
-        mBackend->traceWindowDispatch(std::move(windowDispatchArgs),
-                                      TracedEventArgs{.isSecure = eventState->isSecure});
+        const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
+                                        .targets = eventState->targets};
+        mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(traceArgs));
     } else {
         eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
     }
@@ -222,13 +247,24 @@
 
 void InputTracer::EventState::onEventProcessingComplete() {
     // Write all of the events known so far to the trace.
-    const TracedEventArgs traceArgs{.isSecure = isSecure};
     for (const auto& event : events) {
+        const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
         writeEventToBackend(event, traceArgs, *tracer.mBackend);
     }
     // Write all pending dispatch args to the trace.
     for (const auto& windowDispatchArgs : pendingDispatchArgs) {
-        tracer.mBackend->traceWindowDispatch(windowDispatchArgs, traceArgs);
+        auto tracedEventIt =
+                std::find_if(events.begin(), events.end(),
+                             [id = getId(windowDispatchArgs.eventEntry)](const auto& event) {
+                                 return id == getId(event);
+                             });
+        if (tracedEventIt == events.end()) {
+            LOG(FATAL) << __func__
+                       << ": Failed to find a previously traced event that matches the dispatched "
+                          "event";
+        }
+        const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
+        tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(traceArgs));
     }
     pendingDispatchArgs.clear();
 
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index 4ef6ca6..717bc1f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -66,6 +66,8 @@
         std::vector<const WindowDispatchArgs> pendingDispatchArgs;
         // True if the event is targeting at least one secure window;
         bool isSecure{false};
+        // The list of all possible UIDs that this event could be targeting.
+        std::set<gui::Uid> targets;
     };
 
     // Get the event state associated with a tracking cookie.
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index 6eef12e..3ff7fab 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -21,12 +21,27 @@
 #include <ui/Transform.h>
 
 #include <array>
+#include <set>
 #include <variant>
 #include <vector>
 
 namespace android::inputdispatcher::trace {
 
 /**
+ * Describes the type of this event being traced, with respect to InputDispatcher.
+ */
+enum class EventType {
+    // This is an event that was reported through the InputListener interface or was injected.
+    INBOUND,
+    // This is an event that was synthesized within InputDispatcher; either being derived
+    // from an inbound event (e.g. a split motion event), or synthesized completely
+    // (e.g. a CANCEL event generated when the inbound stream is not canceled).
+    SYNTHESIZED,
+
+    ftl_last = SYNTHESIZED,
+};
+
+/**
  * A representation of an Android KeyEvent used by the tracing backend.
  */
 struct TracedKeyEvent {
@@ -43,6 +58,7 @@
     nsecs_t downTime;
     int32_t flags;
     int32_t repeatCount;
+    EventType eventType;
 };
 
 /**
@@ -69,6 +85,7 @@
     nsecs_t downTime;
     std::vector<PointerProperties> pointerProperties;
     std::vector<PointerCoords> pointerCoords;
+    EventType eventType;
 };
 
 /** A representation of a traced input event. */
@@ -78,6 +95,8 @@
 struct TracedEventArgs {
     // True if the event is targeting at least one secure window.
     bool isSecure;
+    // The list of possible UIDs that this event could be targeting.
+    std::set<gui::Uid> targets;
 };
 
 /** Additional information about an input event being dispatched to a window. */
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 8ef9ca5..b76bec3 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/logging.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <private/android_filesystem_config.h>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -29,24 +30,125 @@
 
 constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
 
+bool isPermanentlyAllowed(gui::Uid uid) {
+    switch (uid.val()) {
+        case AID_SYSTEM:
+        case AID_SHELL:
+        case AID_ROOT:
+            return true;
+        default:
+            return false;
+    }
+}
+
 } // namespace
 
 // --- PerfettoBackend::InputEventDataSource ---
 
-void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) {
-    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+PerfettoBackend::InputEventDataSource::InputEventDataSource() : mInstanceId(sNextInstanceId++) {}
+
+void PerfettoBackend::InputEventDataSource::OnSetup(const InputEventDataSource::SetupArgs& args) {
+    LOG(INFO) << "Setting up perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+    const auto rawConfig = args.config->android_input_event_config_raw();
+    auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig};
+
+    mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig);
 }
 
-void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) {
-    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) {
+    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+}
+
+void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::StopArgs&) {
+    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
 }
 
+void PerfettoBackend::InputEventDataSource::initializeUidMap(GetPackageUid getPackageUid) {
+    if (mUidMap.has_value()) {
+        return;
+    }
+
+    mUidMap = {{}};
+    for (const auto& rule : mConfig.rules) {
+        for (const auto& package : rule.matchAllPackages) {
+            mUidMap->emplace(package, getPackageUid(package));
+        }
+        for (const auto& package : rule.matchAnyPackages) {
+            mUidMap->emplace(package, getPackageUid(package));
+        }
+    }
+}
+
+bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent(
+        const EventType& type) const {
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) {
+        // Ignore all input events.
+        return true;
+    }
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH) &&
+        type != EventType::INBOUND) {
+        // When window dispatch tracing is disabled, ignore any events that are not inbound events.
+        return true;
+    }
+    return false;
+}
+
+TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel(
+        const TracedEventArgs& args) const {
+    // Check for matches with the rules in the order that they are defined.
+    for (const auto& rule : mConfig.rules) {
+        if (ruleMatches(rule, args)) {
+            return rule.level;
+        }
+    }
+    // The event is not traced if it matched zero rules.
+    return TraceLevel::TRACE_LEVEL_NONE;
+}
+
+bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
+                                                        const TracedEventArgs& args) const {
+    // By default, a rule will match all events. Return early if the rule does not match.
+
+    // Match the event if it is directed to a secure window.
+    if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) {
+        return false;
+    }
+
+    // Match the event if all of its target packages are explicitly allowed in the "match all" list.
+    if (!rule.matchAllPackages.empty() &&
+        !std::all_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+            return isPermanentlyAllowed(uid) ||
+                    std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(),
+                                [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // Match the event if any of its target packages are allowed in the "match any" list.
+    if (!rule.matchAnyPackages.empty() &&
+        !std::any_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+            return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(),
+                               [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // The event matches all matchers specified in the rule.
+    return true;
+}
+
 // --- PerfettoBackend ---
 
 std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
 
-PerfettoBackend::PerfettoBackend() {
+std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
+
+PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid)
+      : mGetPackageUid(getPackagesForUid) {
     // Use a once-flag to ensure that the data source is only registered once per boot, since
     // we never unregister the InputEventDataSource.
     std::call_once(sDataSourceRegistrationFlag, []() {
@@ -65,42 +167,65 @@
 
 void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
                                        const TracedEventArgs& args) {
-    if (args.isSecure) {
-        // For now, avoid tracing secure event entirely.
-        return;
-    }
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        dataSource->initializeUidMap(mGetPackageUid);
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEvent = tracePacket->set_android_input_event();
-        auto* dispatchMotion = inputEvent->set_dispatcher_motion_event();
-        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion);
+        auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
+                                          : inputEvent->set_dispatcher_motion_event();
+        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
     });
 }
 
 void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) {
-    if (args.isSecure) {
-        // For now, avoid tracing secure event entirely.
-        return;
-    }
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        dataSource->initializeUidMap(mGetPackageUid);
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEvent = tracePacket->set_android_input_event();
-        auto* dispatchKey = inputEvent->set_dispatcher_key_event();
-        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey);
+        auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
+                                       : inputEvent->set_dispatcher_key_event();
+        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
     });
 }
 
 void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
                                           const TracedEventArgs& args) {
-    if (args.isSecure) {
-        // For now, avoid tracing secure event entirely.
-        return;
-    }
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        dataSource->initializeUidMap(mGetPackageUid);
+        if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEvent = tracePacket->set_android_input_event();
-        auto* dispatchEvent = inputEvent->set_dispatcher_window_dispatch_event();
-        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent);
+        auto* dispatchEvent = isRedacted
+                ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
+                : inputEvent->set_dispatcher_window_dispatch_event();
+        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent,
+                                                                    isRedacted);
     });
 }
 
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index d455375..af1c6b7 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -18,8 +18,12 @@
 
 #include "InputTracingBackendInterface.h"
 
+#include "InputTracingPerfettoBackendConfig.h"
+
+#include <ftl/flags.h>
 #include <perfetto/tracing.h>
 #include <mutex>
+#include <set>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -45,22 +49,46 @@
  */
 class PerfettoBackend : public InputTracingBackendInterface {
 public:
-    PerfettoBackend();
+    using GetPackageUid = std::function<gui::Uid(std::string)>;
+
+    explicit PerfettoBackend(GetPackageUid);
     ~PerfettoBackend() override = default;
 
     void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
     void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
     void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
 
+private:
+    // Implementation of the perfetto data source.
+    // Each instance of the InputEventDataSource represents a different tracing session.
     class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
     public:
-        void OnSetup(const SetupArgs&) override {}
+        explicit InputEventDataSource();
+
+        void OnSetup(const SetupArgs&) override;
         void OnStart(const StartArgs&) override;
         void OnStop(const StopArgs&) override;
+
+        void initializeUidMap(GetPackageUid);
+        bool shouldIgnoreTracedInputEvent(const EventType&) const;
+        inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
+        TraceLevel resolveTraceLevel(const TracedEventArgs&) const;
+
+    private:
+        const int32_t mInstanceId;
+        TraceConfig mConfig;
+
+        bool ruleMatches(const TraceRule&, const TracedEventArgs&) const;
+
+        std::optional<std::map<std::string, gui::Uid>> mUidMap;
     };
 
-private:
+    // TODO(b/330360505): Query the native package manager directly from the data source,
+    //   and remove this.
+    GetPackageUid mGetPackageUid;
+
     static std::once_flag sDataSourceRegistrationFlag;
+    static std::atomic<int32_t> sNextInstanceId;
 };
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
new file mode 100644
index 0000000..536e32b
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 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 <ftl/enum.h>
+#include <ftl/flags.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/** Flags representing the configurations that are enabled in the trace. */
+enum class TraceFlag : uint32_t {
+    // Trace details about input events processed by InputDispatcher.
+    TRACE_DISPATCHER_INPUT_EVENTS = 0x1,
+    // Trace details about an event being sent to a window by InputDispatcher.
+    TRACE_DISPATCHER_WINDOW_DISPATCH = 0x2,
+
+    ftl_last = TRACE_DISPATCHER_WINDOW_DISPATCH,
+};
+
+/** Representation of AndroidInputEventConfig::TraceLevel. */
+using TraceLevel = perfetto::protos::pbzero::AndroidInputEventConfig::TraceLevel;
+
+/** Representation of AndroidInputEventConfig::TraceRule. */
+struct TraceRule {
+    TraceLevel level;
+
+    std::vector<std::string> matchAllPackages;
+    std::vector<std::string> matchAnyPackages;
+    std::optional<bool> matchSecure;
+    std::optional<bool> matchImeConnectionActive;
+};
+
+/**
+ * A complete configuration for a tracing session.
+ *
+ * The trace rules are applied as documented in the perfetto config:
+ *   /external/perfetto/protos/perfetto/config/android/android_input_event_config.proto
+ */
+struct TraceConfig {
+    ftl::Flags<TraceFlag> flags;
+    std::vector<TraceRule> rules;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index b1791b3..1614789 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -38,10 +38,10 @@
 
 template <typename Backend>
 ThreadedBackend<Backend>::ThreadedBackend(Backend&& innerBackend)
-      : mTracerThread(
+      : mBackend(std::move(innerBackend)),
+        mTracerThread(
                 "InputTracer", [this]() { threadLoop(); },
-                [this]() { mThreadWakeCondition.notify_all(); }),
-        mBackend(std::move(innerBackend)) {}
+                [this]() { mThreadWakeCondition.notify_all(); }) {}
 
 template <typename Backend>
 ThreadedBackend<Backend>::~ThreadedBackend() {
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index cab47af..3bfc72b 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -44,7 +44,6 @@
 
 private:
     std::mutex mLock;
-    InputThread mTracerThread;
     bool mThreadExit GUARDED_BY(mLock){false};
     std::condition_variable mThreadWakeCondition;
     Backend mBackend;
@@ -53,6 +52,11 @@
                       TracedEventArgs>;
     std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
 
+    // InputThread stops when its destructor is called. Initialize it last so that it is the
+    // first thing to be destructed. This will guarantee the thread will not access other
+    // members that have already been destructed.
+    InputThread mTracerThread;
+
     void threadLoop();
 };
 
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 2baf576..782c84a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -507,15 +507,7 @@
     }
 
     // Touchscreens and touchpad devices.
-    static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
-            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
-    // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or
-    // at least load this setting from the IDC file.
-    const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier();
-    const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
-            (identifier.product == 0x05c4 || identifier.product == 0x09cc);
-    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
-        classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
+    if (classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 99f9e24..721cdfd 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -198,6 +198,7 @@
     }
 
     void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) {
+        ALOGI("Producing touchpad usage atoms for %zu counters", mCounters.size());
         for (auto& [id, counters] : mCounters) {
             auto [busId, vendorId, productId, versionId] = id;
             addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index fb2db06..e0a7324 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -86,6 +86,8 @@
 
     void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                  const std::set<gui::Uid>& uids) override {}
+
+    gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; }
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 9375e92..9e3a4f1 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -660,6 +660,8 @@
         verify(*mFilteredEvent);
         mFilteredEvent = nullptr;
     }
+
+    gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; }
 };
 } // namespace
 
@@ -5367,6 +5369,94 @@
     window->assertNoEvents();
 }
 
+// This test verifies the occlusion detection for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOrientations) {
+    constexpr static int32_t displayWidth = 400;
+    constexpr static int32_t displayHeight = 800;
+
+    std::shared_ptr<FakeApplicationHandle> untrustedWindowApplication =
+            std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    const auto rotation = GetParam();
+
+    // Set up the display with the specified rotation.
+    const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+    const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+    const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+    const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+                                         logicalDisplayWidth, logicalDisplayHeight);
+    addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+
+    // Create a window that not trusted.
+    const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300);
+
+    const Rect untrustedWindowFrameInDisplay =
+            displayTransform.inverse().transform(untrustedWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> untrustedWindow =
+            sp<FakeWindowHandle>::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow",
+                                       ADISPLAY_ID_DEFAULT);
+    untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform);
+    untrustedWindow->setTrustedOverlay(false);
+    untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+    untrustedWindow->setTouchable(false);
+    untrustedWindow->setAlpha(1.0f);
+    untrustedWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    addWindow(untrustedWindow);
+
+    // Create a simple app window below the untrusted window.
+    const Rect simpleAppWindowFrameInLogicalDisplay(0, 0, 300, 600);
+    const Rect simpleAppWindowFrameInDisplay =
+            displayTransform.inverse().transform(simpleAppWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> simpleAppWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SimpleAppWindow",
+                                       ADISPLAY_ID_DEFAULT);
+    simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform);
+    simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+    addWindow(simpleAppWindow);
+
+    // The following points in logical display space should be inside the untrusted window, so
+    // the simple window could not receive events that coordinate is these point.
+    static const std::array<vec2, 4> untrustedPoints{
+            {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+
+    for (const auto untrustedPoint : untrustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(untrustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+    }
+    untrustedWindow->assertNoEvents();
+    simpleAppWindow->assertNoEvents();
+    // The following points in logical display space should be outside the untrusted window, so
+    // the simple window should receive events that coordinate is these point.
+    static const std::array<vec2, 5> trustedPoints{
+            {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+    for (const auto trustedPoint : trustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(trustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+                                           AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                                     {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT,
+                                         AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+    }
+    untrustedWindow->assertNoEvents();
+}
+
 // Run the precision tests for all rotations.
 INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
                          InputDispatcherDisplayOrientationFixture,
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 8ddb672..3b2565e 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -46,6 +46,7 @@
 constexpr int32_t ANOTHER_DISPLAY_ID = 10;
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS;
 
 const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
                                    .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
@@ -759,12 +760,28 @@
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+using StylusFixtureParam =
+        std::tuple</*name*/ std::string_view, /*source*/ uint32_t, ControllerType>;
+
+class StylusTestFixture : public PointerChoreographerTest,
+                          public ::testing::WithParamInterface<StylusFixtureParam> {};
+
+INSTANTIATE_TEST_SUITE_P(PointerChoreographerTest, StylusTestFixture,
+                         ::testing::Values(std::make_tuple("DirectStylus", AINPUT_SOURCE_STYLUS,
+                                                           ControllerType::STYLUS),
+                                           std::make_tuple("DrawingTablet", DRAWING_TABLET_SOURCE,
+                                                           ControllerType::MOUSE)),
+                         [](const testing::TestParamInfo<StylusFixtureParam>& p) {
+                             return std::string{std::get<0>(p.param)};
+                         });
+
+TEST_P(StylusTestFixture, WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Disable stylus pointer icon and add a stylus device.
     mChoreographer.setStylusPointerIconEnabled(false);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     assertPointerControllerNotCreated();
 
     // Enable stylus pointer icon. PointerController still should not be created.
@@ -772,25 +789,25 @@
     assertPointerControllerNotCreated();
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+TEST_P(StylusTestFixture, WhenStylusHoverEventOccursCreatesPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Add a stylus device and enable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     assertPointerControllerNotCreated();
 
     // Emit hover event. Now PointerController should be created.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    assertPointerControllerCreated(controllerType);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+TEST_F(PointerChoreographerTest, StylusHoverEventWhenStylusPointerIconDisabled) {
     // Add a stylus device and disable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -807,25 +824,43 @@
     assertPointerControllerNotCreated();
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
-    // Make sure the PointerController is created.
+TEST_F(PointerChoreographerTest, DrawingTabletHoverEventWhenStylusPointerIconDisabled) {
+    // Add a drawing tablet and disable stylus pointer icon.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
-    mChoreographer.setStylusPointerIconEnabled(true);
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Drawing tablets are not affected by "stylus pointer icon" setting.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
                     .pointer(STYLUS_POINTER)
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
                     .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_P(StylusTestFixture, WhenStylusDeviceIsRemovedRemovesPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Remove the device.
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+TEST_F(PointerChoreographerTest, StylusPointerIconDisabledRemovesPointerController) {
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -843,38 +878,59 @@
     assertPointerControllerRemoved(pc);
 }
 
-TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+TEST_F(PointerChoreographerTest,
+       StylusPointerIconDisabledDoesNotRemoveDrawingTabletPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    // Disable stylus pointer icon. This should not affect drawing tablets.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotRemoved(pc);
+}
+
+TEST_P(StylusTestFixture, SetsViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Set viewport.
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is set for the PointerController.
     pc->assertViewportSet(DISPLAY_ID);
 }
 
-TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is unset.
     pc->assertViewportNotSet();
@@ -886,19 +942,19 @@
     pc->assertViewportSet(DISPLAY_ID);
 }
 
-TEST_F(PointerChoreographerTest,
-       WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Check that viewport is unset.
     pc->assertViewportNotSet();
@@ -910,24 +966,25 @@
     pc->assertViewportNotSet();
 }
 
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointer) {
+    const auto& [name, source, controllerType] = GetParam();
+
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
     // Emit hover enter event. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
 
     // Emit hover move event. After bounds are set, PointerController will update the position.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -937,7 +994,7 @@
 
     // Emit hover exit event.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -946,38 +1003,38 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointerForTwoDisplays) {
+    const auto& [name, source, controllerType] = GetParam();
+
     mChoreographer.setStylusPointerIconEnabled(true);
     // Add two stylus devices associated to different displays.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+             {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, ANOTHER_DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
 
     // Emit hover event with first device. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto firstDisplayPc = assertPointerControllerCreated(controllerType);
 
     // Emit hover event with second device. This is for creating PointerController.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(ANOTHER_DISPLAY_ID)
-                    .build());
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(ANOTHER_DISPLAY_ID)
+                                        .build());
 
     // There should be another PointerController created.
-    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    auto secondDisplayPc = assertPointerControllerCreated(controllerType);
 
     // Emit hover event with first device.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
                     .deviceId(DEVICE_ID)
                     .displayId(DISPLAY_ID)
@@ -989,7 +1046,7 @@
 
     // Emit hover event with second device.
     mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
                     .deviceId(SECOND_DEVICE_ID)
                     .displayId(ANOTHER_DISPLAY_ID)
@@ -1004,19 +1061,20 @@
     ASSERT_TRUE(firstDisplayPc->isPointerShown());
 }
 
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+TEST_P(StylusTestFixture, WhenStylusDeviceIsResetRemovesPointer) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure the PointerController is created and there is a pointer.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     ASSERT_TRUE(pc->isPointerShown());
 
     // Reset the device and see the pointer controller was removed.
@@ -1460,19 +1518,20 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
+TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there is a PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     pc->assertPointerIconNotSet();
 
     // Set pointer icon for the device.
@@ -1485,28 +1544,28 @@
     pc->assertPointerIconNotSet();
 
     // The stylus stops hovering. This should cause the icon to be reset.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
     pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED);
 }
 
-TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+TEST_P(StylusTestFixture, SetsCustomPointerIconForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there is a PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(controllerType);
     pc->assertCustomPointerIconNotSet();
 
     // Set custom pointer icon for the device.
@@ -1522,28 +1581,28 @@
     pc->assertCustomPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+TEST_P(StylusTestFixture, SetsPointerIconForTwoStyluses) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there are two StylusPointerControllers. They can be on a same display.
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+             {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto firstStylusPc = assertPointerControllerCreated(controllerType);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto secondStylusPc = assertPointerControllerCreated(controllerType);
 
     // Set pointer icon for one stylus.
     ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1557,14 +1616,16 @@
     firstStylusPc->assertPointerIconNotSet();
 }
 
-TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     // Make sure there are PointerControllers for a mouse and a stylus.
     mChoreographer.setStylusPointerIconEnabled(true);
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+              generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyMotion(
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
@@ -1573,13 +1634,12 @@
                     .displayId(ADISPLAY_ID_NONE)
                     .build());
     auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(SECOND_DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
-    auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto stylusPc = assertPointerControllerCreated(controllerType);
 
     // Set pointer icon for the mouse.
     ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1688,7 +1748,9 @@
     ASSERT_FALSE(touchpadPc->isPointerShown());
 }
 
-TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) {
+TEST_P(StylusTestFixture, SetPointerIconVisibilityHidesPointerForStylus) {
+    const auto& [name, source, controllerType] = GetParam();
+
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setStylusPointerIconEnabled(true);
 
@@ -1696,15 +1758,14 @@
     mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
 
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
-                    .pointer(STYLUS_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(DISPLAY_ID)
-                    .build());
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
     ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
-    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    auto pc = assertPointerControllerCreated(controllerType);
     pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
 
     // The pointer should not be visible.
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 3ea08fe..4f65e77 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -20,7 +20,6 @@
         "PowerHintSessionWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
-        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
@@ -52,6 +51,10 @@
         "android.hardware.power@1.3",
     ],
 
+    whole_static_libs: [
+        "android.os.hintmanager_aidl-ndk",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
deleted file mode 100644
index bd2b10a..0000000
--- a/services/powermanager/WorkDuration.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "WorkDuration"
-
-#include <android/WorkDuration.h>
-#include <android/performance_hint.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
-
-namespace android::os {
-
-WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
-                           int64_t cpuDurationNanos, int64_t gpuDurationNanos)
-      : timestampNanos(0),
-        actualTotalDurationNanos(totalDurationNanos),
-        workPeriodStartTimestampNanos(startTimestampNanos),
-        actualCpuDurationNanos(cpuDurationNanos),
-        actualGpuDurationNanos(gpuDurationNanos) {}
-
-status_t WorkDuration::writeToParcel(Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-
-    parcel->writeInt64(workPeriodStartTimestampNanos);
-    parcel->writeInt64(actualTotalDurationNanos);
-    parcel->writeInt64(actualCpuDurationNanos);
-    parcel->writeInt64(actualGpuDurationNanos);
-    parcel->writeInt64(timestampNanos);
-    return OK;
-}
-
-status_t WorkDuration::readFromParcel(const Parcel*) {
-    return INVALID_OPERATION;
-}
-
-} // namespace android::os
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
deleted file mode 100644
index 26a575f..0000000
--- a/services/powermanager/include/android/WorkDuration.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (C) 2023 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/Parcelable.h>
-#include <math.h>
-
-struct AWorkDuration {};
-
-namespace android::os {
-
-/**
- * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
- * binder calls.
- * This file needs to be kept in sync with the WorkDuration in
- * frameworks/base/core/java/android/os/WorkDuration.java
- */
-struct WorkDuration : AWorkDuration, android::Parcelable {
-    WorkDuration() = default;
-    ~WorkDuration() = default;
-
-    WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
-                 int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
-    status_t writeToParcel(Parcel* parcel) const override;
-    status_t readFromParcel(const Parcel* parcel) override;
-
-    inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
-        return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
-                actualTotalDurationNanos == other.actualTotalDurationNanos &&
-                actualCpuDurationNanos == other.actualCpuDurationNanos &&
-                actualGpuDurationNanos == other.actualGpuDurationNanos;
-    }
-
-    bool operator==(const WorkDuration& other) const {
-        return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
-    }
-
-    bool operator!=(const WorkDuration& other) const { return !(*this == other); }
-
-    friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
-        os << "{"
-           << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
-           << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
-           << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
-           << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
-           << ", timestampNanos: " << workDuration.timestampNanos << "}";
-        return os;
-    }
-
-    int64_t timestampNanos;
-    int64_t actualTotalDurationNanos;
-    int64_t workPeriodStartTimestampNanos;
-    int64_t actualCpuDurationNanos;
-    int64_t actualGpuDurationNanos;
-};
-
-} // namespace android::os
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 7c10fa5..e32cc02 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -73,6 +73,9 @@
     // TODO(b/121291683): These will become private/internal
     virtual void preComposition(CompositionRefreshArgs&) = 0;
 
+    // Resolves any unfulfilled promises for release fences
+    virtual void postComposition(CompositionRefreshArgs&) = 0;
+
     virtual FeatureFlags getFeatureFlags() const = 0;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 843b5c5..dd0f985 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -34,12 +34,6 @@
 using Layers = std::vector<sp<compositionengine::LayerFE>>;
 using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
 
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    std::vector<int32_t> layerIds;
-};
-
 // Interface of composition engine power hint callback.
 struct ICEPowerCallback {
     virtual void notifyCpuLoadUp() = 0;
@@ -101,8 +95,6 @@
     // TODO (b/255601557): Calculate per display.
     std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 
-    std::vector<BorderRenderInfo> borderInfoList;
-
     bool hasTrustedPresentationListener = false;
 
     ICEPowerCallback* powerCallback = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a499928..4e080b3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -133,6 +133,15 @@
         uint64_t frameNumber = 0;
     };
 
+    // Describes the states of the release fence. Checking the states allows checks
+    // to ensure that set_value() is not called on the same promise multiple times,
+    // and can indicate if the promise has been fulfilled.
+    enum class ReleaseFencePromiseStatus {
+        UNINITIALIZED, // Promise not created
+        INITIALIZED,   // Promise created, fence has not been set
+        FULFILLED      // Promise fulfilled, fence is set
+    };
+
     // Returns the LayerSettings to pass to RenderEngine::drawLayers. The state may contain shadows
     // casted by the layer or the content of the layer itself. If the layer does not render then an
     // empty optional will be returned.
@@ -142,6 +151,19 @@
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack) = 0;
 
+    // Initializes a promise for a buffer release fence and provides the future for that
+    // fence. This should only be called when a promise has not yet been created, or
+    // after the previous promise has already been fulfilled. Attempting to call this
+    // when an existing promise is INITIALIZED will fail because the promise has not
+    // yet been fulfilled.
+    virtual ftl::Future<FenceResult> createReleaseFenceFuture() = 0;
+
+    // Sets promise with its buffer's release fence
+    virtual void setReleaseFence(const FenceResult& releaseFence) = 0;
+
+    // Checks if the buffer's release fence has been set
+    virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
+
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index f1d6f52..e7d0afc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -321,7 +321,9 @@
             const Region& flashRegion,
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+    virtual void setHintSessionGpuStart(TimePoint startTime) = 0;
     virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
+    virtual void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) = 0;
     virtual bool isPowerHintSessionEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index c699557..45208dd 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -48,6 +48,8 @@
 
     void preComposition(CompositionRefreshArgs&) override;
 
+    void postComposition(CompositionRefreshArgs&) override;
+
     FeatureFlags getFeatureFlags() const override;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 2dc9a1a..eaffa9e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -93,7 +93,9 @@
 
 private:
     bool isPowerHintSessionEnabled() override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 911d67b..3671f15 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -144,7 +144,9 @@
             std::vector<LayerFE*>& outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     bool isPowerHintSessionEnabled() override;
     void dumpBase(std::string&) const;
 
@@ -162,7 +164,6 @@
 
 private:
     void dirtyEntireOutput();
-    void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
     compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
     void finishPrepareFrame();
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 6b1c318..f8ffde1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -34,7 +34,6 @@
 
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/ProjectionSpace.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -166,8 +165,6 @@
 
     bool treat170mAsSrgb = false;
 
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
-
     uint64_t lastOutputLayerHash = 0;
     uint64_t outputLayerHash = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index 9b2387b..a1b7282 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -52,6 +52,7 @@
     MOCK_METHOD1(updateCursorAsync, void(CompositionRefreshArgs&));
 
     MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+    MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
 
     MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 1b8cc27..05a5d38 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -20,6 +20,7 @@
 #include <compositionengine/LayerFECompositionState.h>
 #include <gmock/gmock.h>
 #include <ui/Fence.h>
+#include "ui/FenceResult.h"
 
 namespace android::compositionengine::mock {
 
@@ -52,6 +53,9 @@
     MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, ui::LayerStack),
                 (override));
 
+    MOCK_METHOD0(createReleaseFenceFuture, ftl::Future<FenceResult>());
+    MOCK_METHOD1(setReleaseFence, void(const FenceResult&));
+    MOCK_METHOD0(getReleaseFencePromiseStatus, LayerFE::ReleaseFencePromiseStatus());
     MOCK_CONST_METHOD0(getDebugName, const char*());
     MOCK_CONST_METHOD0(getSequence, int32_t());
     MOCK_CONST_METHOD0(hasRoundedCorners, bool());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95ea3a4..019a058 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -134,7 +134,9 @@
     MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
     MOCK_METHOD1(setTreat170mAsSrgb, void(bool));
+    MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime));
     MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
+    MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
     MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index b470208..4c77687 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -162,6 +162,7 @@
             future.get();
         }
     }
+    postComposition(args);
 }
 
 void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
@@ -192,6 +193,34 @@
     mNeedsAnotherUpdate = needsAnotherUpdate;
 }
 
+// If a buffer is latched but the layer is not presented, such as when
+// obscured by another layer, the previous buffer needs to be released. We find
+// these buffers and fire a NO_FENCE to release it. This ensures that all
+// promises for buffer releases are fulfilled at the end of composition.
+void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        ATRACE_CALL();
+        ALOGV(__FUNCTION__);
+
+        for (auto& layerFE : args.layers) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+
+        // List of layersWithQueuedFrames does not necessarily overlap with
+        // list of layers, so those layersWithQueuedFrames also need any
+        // unfulfilled promises to be resolved for completeness.
+        for (auto& layerFE : args.layersWithQueuedFrames) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+    }
+}
+
 FeatureFlags CompositionEngine::getFeatureFlags() const {
     return {};
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 6428d08..3d35704 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -252,10 +252,6 @@
     auto& hwc = getCompositionEngine().getHwComposer();
     const bool requiresClientComposition = anyLayersRequireClientComposition();
 
-    if (isPowerHintSessionEnabled()) {
-        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
-    }
-
     const TimePoint hwcValidateStartTime = TimePoint::now();
 
     if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
@@ -416,10 +412,20 @@
     return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
 }
 
+// For ADPF GPU v0 this is expected to set start time to when the GPU commands are submitted with
+// fence returned, i.e. when RenderEngine flushes the commands and returns the draw fence.
+void Display::setHintSessionGpuStart(TimePoint startTime) {
+    mPowerAdvisor->setGpuStartTime(mId, startTime);
+}
+
 void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) {
     mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence));
 }
 
+void Display::setHintSessionRequiresRenderEngine(bool requiresRenderEngine) {
+    mPowerAdvisor->setRequiresRenderEngine(mId, requiresRenderEngine);
+}
+
 void Display::finishFrame(GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 921e05d..1f01b57 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -16,6 +16,7 @@
 
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -463,6 +464,10 @@
     setColorTransform(refreshArgs);
     beginFrame();
 
+    if (isPowerHintSessionEnabled()) {
+        // always reset the flag before the composition prediction
+        setHintSessionRequiresRenderEngine(false);
+    }
     GpuCompositionResult result;
     const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs);
     if (predictCompositionStrategy) {
@@ -818,44 +823,6 @@
             forceClientComposition = false;
         }
     }
-
-    updateCompositionStateForBorder(refreshArgs);
-}
-
-void Output::updateCompositionStateForBorder(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    std::unordered_map<int32_t, const Region*> layerVisibleRegionMap;
-    // Store a map of layerId to their computed visible region.
-    for (auto* layer : getOutputLayersOrderedByZ()) {
-        int layerId = (layer->getLayerFE()).getSequence();
-        layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion);
-    }
-    OutputCompositionState& outputCompositionState = editState();
-    outputCompositionState.borderInfoList.clear();
-    bool clientComposeTopLayer = false;
-    for (const auto& borderInfo : refreshArgs.borderInfoList) {
-        renderengine::BorderRenderInfo info;
-        for (const auto& id : borderInfo.layerIds) {
-            info.combinedRegion.orSelf(*(layerVisibleRegionMap[id]));
-        }
-
-        if (!info.combinedRegion.isEmpty()) {
-            info.width = borderInfo.width;
-            info.color = borderInfo.color;
-            outputCompositionState.borderInfoList.emplace_back(std::move(info));
-            clientComposeTopLayer = true;
-        }
-    }
-
-    // In this situation we must client compose the top layer instead of using hwc
-    // because we want to draw the border above all else.
-    // This could potentially cause a bit of a performance regression if the top
-    // layer would have been rendered using hwc originally.
-    // TODO(b/227656283): Measure system's performance before enabling the border feature
-    if (clientComposeTopLayer) {
-        auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1);
-        (topLayer->editState()).forceClientComposition = true;
-    }
 }
 
 void Output::planComposition() {
@@ -1247,8 +1214,7 @@
     if (!optReadyFence) {
         return;
     }
-
-    if (isPowerHintSessionEnabled()) {
+    if (isPowerHintSessionEnabled() && !FlagManager::getInstance().adpf_gpu_sf()) {
         // get fence end time to know when gpu is complete in display
         setHintSessionGpuFence(
                 std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
@@ -1392,8 +1358,20 @@
         // If rendering was not successful, remove the request from the cache.
         mClientCompositionRequestCache->remove(tex->getBuffer()->getId());
     }
-
     const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
+    if (isPowerHintSessionEnabled()) {
+        if (fence != Fence::NO_FENCE && fence->isValid() &&
+            !outputCompositionState.reusedClientComposition) {
+            setHintSessionRequiresRenderEngine(true);
+            if (FlagManager::getInstance().adpf_gpu_sf()) {
+                // the order of the two calls here matters as we should check if the previously
+                // tracked fence has signaled first and archive the previous start time
+                setHintSessionGpuStart(TimePoint::now());
+                setHintSessionGpuFence(
+                        std::make_unique<FenceTime>(sp<Fence>::make(dup(fence->get()))));
+            }
+        }
+    }
 
     if (auto timeStats = getCompositionEngine().getTimeStats()) {
         if (fence->isValid()) {
@@ -1443,13 +1421,6 @@
 
     // Compute the global color transform matrix.
     clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    for (auto& info : outputState.borderInfoList) {
-        renderengine::BorderRenderInfo borderInfo;
-        borderInfo.width = info.width;
-        borderInfo.color = info.color;
-        borderInfo.combinedRegion = info.combinedRegion;
-        clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
-    }
     clientCompositionDisplay.deviceHandlesColorTransform =
             outputState.usesDeviceComposition || getSkipColorTransform();
     return clientCompositionDisplay;
@@ -1576,10 +1547,18 @@
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionGpuStart(TimePoint) {
+    // The base class does nothing with this call.
+}
+
 void Output::setHintSessionGpuFence(std::unique_ptr<FenceTime>&&) {
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionRequiresRenderEngine(bool) {
+    // The base class does nothing with this call.
+}
+
 bool Output::isPowerHintSessionEnabled() {
     return false;
 }
@@ -1621,9 +1600,13 @@
             releaseFence =
                     Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
         }
-        layer->getLayerFE()
-                .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
-                                  outputState.layerFilter.layerStack);
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            layer->getLayerFE().setReleaseFence(releaseFence);
+        } else {
+            layer->getLayerFE()
+                    .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+                                      outputState.layerFilter.layerStack);
+        }
     }
 
     // We've got a list of layers needing fences, that are disjoint with
@@ -1631,8 +1614,12 @@
     // supply them with the present fence.
     for (auto& weakLayer : mReleasedLayers) {
         if (const auto layer = weakLayer.promote()) {
-            layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
-                                    outputState.layerFilter.layerStack);
+            if (FlagManager::getInstance().ce_fence_promise()) {
+                layer->setReleaseFence(frame.presentFence);
+            } else {
+                layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+                                        outputState.layerFilter.layerStack);
+            }
         }
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 39cf671..6683e67 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -67,11 +67,6 @@
 
     out.append("\n   ");
     dumpVal(out, "treat170mAsSrgb", treat170mAsSrgb);
-
-    out.append("\n");
-    for (const auto& borderRenderInfo : borderInfoList) {
-        dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion);
-    }
     out.append("\n");
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 042010e..639164d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -28,6 +28,7 @@
 
 #include "MockHWComposer.h"
 #include "TimeStats/TimeStats.h"
+#include "gmock/gmock.h"
 
 #include <variant>
 
@@ -90,14 +91,16 @@
         // These are the overridable functions CompositionEngine::present() may
         // call, and have separate test coverage.
         MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+        MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
     };
 
     StrictMock<CompositionEnginePartialMock> mEngine;
 };
 
 TEST_F(CompositionEnginePresentTest, worksWithEmptyRequest) {
-    // present() always calls preComposition()
+    // present() always calls preComposition() and postComposition()
     EXPECT_CALL(mEngine, preComposition(Ref(mRefreshArgs)));
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
 
     mEngine.present(mRefreshArgs);
 }
@@ -126,6 +129,9 @@
     EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
 
+    // present() always calls postComposition()
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
+
     mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
     mEngine.present(mRefreshArgs);
 }
@@ -481,5 +487,29 @@
     mEngine.present(mRefreshArgs);
 }
 
+struct CompositionEnginePostCompositionTest : public CompositionEngineTest {
+    sp<StrictMock<mock::LayerFE>> mLayer1FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer2FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer3FE = sp<StrictMock<mock::LayerFE>>::make();
+};
+
+TEST_F(CompositionEnginePostCompositionTest, postCompositionReleasesAllFences) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    EXPECT_CALL(*mLayer1FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer2FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer3FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::INITIALIZED));
+    mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
+
+    EXPECT_CALL(*mLayer1FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer2FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer3FE, setReleaseFence(_)).Times(1);
+
+    mEngine.postComposition(mRefreshArgs);
+}
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index 7253354..d0843a2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -42,6 +42,7 @@
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -51,8 +52,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 799c7ed..6be245e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2017,8 +2017,15 @@
         MOCK_METHOD0(presentFrameAndReleaseLayers, void());
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
     };
 
+    OutputPresentTest() {
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+    }
+
     StrictMock<OutputPartialMock> mOutput;
 };
 
@@ -2032,6 +2039,7 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false));
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2052,6 +2060,7 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true));
     EXPECT_CALL(mOutput, prepareFrameAsync());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2989,6 +2998,9 @@
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
                      bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
+        MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
     };
 
     OutputFinishFrameTest() {
@@ -2997,6 +3009,7 @@
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
     }
 
     StrictMock<OutputPartialMock> mOutput;
@@ -3014,6 +3027,7 @@
 }
 
 TEST_F(OutputFinishFrameTest, takesEarlyOutifComposeSurfacesReturnsNoFence) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     EXPECT_CALL(mOutput, updateProtectedContentState());
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
@@ -3023,7 +3037,8 @@
     mOutput.finishFrame(std::move(result));
 }
 
-TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFenceWithAdpfGpuOff) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
@@ -3031,6 +3046,23 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3039,6 +3071,7 @@
 
 TEST_F(OutputFinishFrameTest, queuesBufferWithHdrSdrRatio) {
     SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
@@ -3058,6 +3091,7 @@
             .WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
 
     impl::GpuCompositionResult result;
@@ -3065,9 +3099,11 @@
 }
 
 TEST_F(OutputFinishFrameTest, predictionSucceeded) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
     InSequence seq;
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3075,6 +3111,7 @@
 }
 
 TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::FAIL;
 
@@ -3090,6 +3127,7 @@
                 composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer,
                                 Eq(ByRef(result.fence))))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
     mOutput.finishFrame(std::move(result));
 }
@@ -3164,6 +3202,8 @@
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
     // Simulate getting release fences from each layer, and ensure they are passed to the
     // front-end layer interface for each layer correctly.
 
@@ -3205,7 +3245,51 @@
     mOutput.presentFrameAndReleaseLayers();
 }
 
+TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+    // Simulate getting release fences from each layer, and ensure they are passed to the
+    // front-end layer interface for each layer correctly.
+
+    mOutput.mState.isEnabled = true;
+
+    // Create three unique fence instances
+    sp<Fence> layer1Fence = sp<Fence>::make();
+    sp<Fence> layer2Fence = sp<Fence>::make();
+    sp<Fence> layer3Fence = sp<Fence>::make();
+
+    Output::FrameFences frameFences;
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Compare the pointers values of each fence to make sure the correct ones
+    // are passed. This happens to work with the current implementation, but
+    // would not survive certain calls like Fence::merge() which would return a
+    // new instance.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence(_))
+            .WillOnce([&layer1Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer1Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence(_))
+            .WillOnce([&layer2Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer2Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence(_))
+            .WillOnce([&layer3Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer3Fence), releaseFence);
+            });
+
+    mOutput.presentFrameAndReleaseLayers();
+}
+
 TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3228,7 +3312,35 @@
     mOutput.presentFrameAndReleaseLayers();
 }
 
+TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    Output::FrameFences frameFences;
+    frameFences.clientTargetAcquireFence = sp<Fence>::make();
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Fence::merge is called, and since none of the fences are actually valid,
+    // Fence::NO_FENCE is returned and passed to each setReleaseFence() call.
+    // This is the best we can do without creating a real kernel fence object.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return());
+    mOutput.presentFrameAndReleaseLayers();
+}
+
 TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3276,6 +3388,54 @@
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
 }
 
+TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    // This should happen even if there are no (current) output layers.
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // Load up the released layers with some mock instances
+    sp<StrictMock<mock::LayerFE>> releasedLayer1 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer2 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer3 = sp<StrictMock<mock::LayerFE>>::make();
+    Output::ReleasedLayers layers;
+    layers.push_back(releasedLayer1);
+    layers.push_back(releasedLayer2);
+    layers.push_back(releasedLayer3);
+    mOutput.setReleasedLayers(std::move(layers));
+
+    // Set up a fake present fence
+    sp<Fence> presentFence = sp<Fence>::make();
+    Output::FrameFences frameFences;
+    frameFences.presentFence = presentFence;
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Each released layer should be given the presentFence.
+    EXPECT_CALL(*releasedLayer1, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer2, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer3, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+
+    mOutput.presentFrameAndReleaseLayers();
+
+    // After the call the list of released layers should have been cleared.
+    EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
+}
+
 /*
  * Output::composeSurfaces()
  */
@@ -3293,8 +3453,10 @@
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+        MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime), (override));
         MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
                     (override));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool), (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
     };
 
@@ -3325,6 +3487,7 @@
         EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
         EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
                 .WillRepeatedly(ReturnRef(kHdrCapabilities));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
     }
 
     struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
@@ -3590,11 +3753,15 @@
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
     // We do not expect another call to draw layers.
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     verify().execute().expectAFenceWasReturned();
     EXPECT_TRUE(mOutput.mState.reusedClientComposition);
 }
 
-TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChangesWithAdpfGpuOff) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
     LayerFE::LayerSettings r1;
     LayerFE::LayerSettings r2;
 
@@ -3618,14 +3785,62 @@
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
             .WillOnce(Return(mOutputBuffer))
             .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
     EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
                                 const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
-                return ftl::yield<FenceResult>(Fence::NO_FENCE);
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
             });
 
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
+    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    const auto otherOutputBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
+            .WillOnce(Return(mOutputBuffer))
+            .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
+                                base::unique_fd&&) -> ftl::Future<FenceResult> {
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
+            });
+
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index faa5197..96cf84c 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -270,27 +270,30 @@
         return;
     }
     ATRACE_CALL();
-    std::optional<Duration> actualDuration = estimateWorkDuration();
-    if (!actualDuration.has_value() || actualDuration < 0ns) {
+    std::optional<WorkDuration> actualDuration = estimateWorkDuration();
+    if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
-    actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
-    mActualDuration = actualDuration;
-
+    actualDuration->durationNanos += sTargetSafetyMargin.ns();
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDuration->ns());
-        ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns());
-        ATRACE_INT64("Reported duration", actualDuration->ns());
+        ATRACE_INT64("Measured duration", actualDuration->durationNanos);
+        ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+        ATRACE_INT64("Reported duration", actualDuration->durationNanos);
+        if (FlagManager::getInstance().adpf_gpu_sf()) {
+            ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+            ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+        }
         ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
         ATRACE_INT64("Reported target error term",
-                     Duration{*actualDuration - mLastTargetDurationSent}.ns());
+                     actualDuration->durationNanos - mLastTargetDurationSent.ns());
     }
 
-    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
-          " with error: %" PRId64,
-          actualDuration->ns(), mLastTargetDurationSent.ns(),
-          Duration{*actualDuration - mLastTargetDurationSent}.ns());
+    ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
+          " on reported target: %" PRId64 " with error: %" PRId64,
+          actualDuration->durationNanos, actualDuration->cpuDurationNanos,
+          actualDuration->gpuDurationNanos, mLastTargetDurationSent.ns(),
+          actualDuration->durationNanos - mLastTargetDurationSent.ns());
 
     if (mTimingTestingMode) {
         mDelayReportActualMutexAcquisitonPromise.get_future().wait();
@@ -303,17 +306,7 @@
             ALOGV("Hint session not running and could not be started, skipping");
             return;
         }
-
-        WorkDuration duration{
-                .timeStampNanos = TimePoint::now().ns(),
-                // TODO(b/284324521): Correctly calculate total duration.
-                .durationNanos = actualDuration->ns(),
-                .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
-                .cpuDurationNanos = actualDuration->ns(),
-                // TODO(b/284324521): Calculate RenderEngine GPU time.
-                .gpuDurationNanos = 0,
-        };
-        mHintSessionQueue.push_back(duration);
+        mHintSessionQueue.push_back(*actualDuration);
 
         auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
         if (!ret.isOk()) {
@@ -348,11 +341,36 @@
     return ensurePowerHintSessionRunning();
 }
 
-void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+void PowerAdvisor::setGpuStartTime(DisplayId displayId, TimePoint startTime) {
     DisplayTimingData& displayData = mDisplayTimingData[displayId];
     if (displayData.gpuEndFenceTime) {
         nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
         if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
+            for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
+                if (!otherDisplayData.lastValidGpuStartTime.has_value() ||
+                    !otherDisplayData.lastValidGpuEndTime.has_value())
+                    continue;
+                if ((*otherDisplayData.lastValidGpuStartTime < *displayData.gpuStartTime) &&
+                    (*otherDisplayData.lastValidGpuEndTime > *displayData.gpuStartTime)) {
+                    displayData.lastValidGpuStartTime = *otherDisplayData.lastValidGpuEndTime;
+                    break;
+                }
+            }
+        }
+        displayData.gpuEndFenceTime = nullptr;
+    }
+    displayData.gpuStartTime = startTime;
+}
+
+void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    if (displayData.gpuEndFenceTime && !FlagManager::getInstance().adpf_gpu_sf()) {
+        nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
+        if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
             for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
                 // If the previous display started before us but ended after we should have
                 // started, then it likely delayed our start time and we must compensate for that.
@@ -365,12 +383,12 @@
                     break;
                 }
             }
-            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
-            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
         }
     }
     displayData.gpuEndFenceTime = std::move(fenceTime);
-    displayData.gpuStartTime = TimePoint::now();
+    if (!FlagManager::getInstance().adpf_gpu_sf()) {
+        displayData.gpuStartTime = TimePoint::now();
+    }
 }
 
 void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
@@ -391,9 +409,8 @@
     mDisplayTimingData[displayId].skippedValidate = skipped;
 }
 
-void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
-                                                bool requiresClientComposition) {
-    mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
+void PowerAdvisor::setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) {
+    mDisplayTimingData[displayId].requiresRenderEngine = requiresRenderEngine;
 }
 
 void PowerAdvisor::setExpectedPresentTime(TimePoint expectedPresentTime) {
@@ -401,8 +418,8 @@
 }
 
 void PowerAdvisor::setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) {
-    mLastSfPresentEndTime = presentEndTime;
     mLastPresentFenceTime = presentFenceTime;
+    mLastSfPresentEndTime = presentEndTime;
 }
 
 void PowerAdvisor::setFrameDelay(Duration frameDelayDuration) {
@@ -443,7 +460,7 @@
     return sortedDisplays;
 }
 
-std::optional<Duration> PowerAdvisor::estimateWorkDuration() {
+std::optional<WorkDuration> PowerAdvisor::estimateWorkDuration() {
     if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) {
         return std::nullopt;
     }
@@ -462,11 +479,10 @@
     // used to accumulate gpu time as we iterate over the active displays
     std::optional<TimePoint> estimatedGpuEndTime;
 
-    // The timing info for the previously calculated display, if there was one
-    std::optional<DisplayTimeline> previousDisplayTiming;
     std::vector<DisplayId>&& displayIds =
             getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
     DisplayTimeline displayTiming;
+    std::optional<GpuTimeline> firstGpuTimeline;
 
     // Iterate over the displays that use hwc in the same order they are presented
     for (DisplayId displayId : displayIds) {
@@ -478,14 +494,6 @@
 
         displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
 
-        // If this is the first display, include the duration before hwc present starts
-        if (!previousDisplayTiming.has_value()) {
-            estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0];
-        } else { // Otherwise add the time since last display's hwc present finished
-            estimatedHwcEndTime +=
-                    displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime;
-        }
-
         // Update predicted present finish time with this display's present time
         estimatedHwcEndTime = displayTiming.hwcPresentEndTime;
 
@@ -500,6 +508,9 @@
         // Estimate the reference frame's gpu timing
         auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
         if (gpuTiming.has_value()) {
+            if (!firstGpuTimeline.has_value()) {
+                firstGpuTimeline = gpuTiming;
+            }
             previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
 
             // Estimate the prediction frame's gpu end time from the reference frame
@@ -507,9 +518,7 @@
                                            estimatedGpuEndTime.value_or(TimePoint{0ns})) +
                     gpuTiming->duration;
         }
-        previousDisplayTiming = displayTiming;
     }
-    ATRACE_INT64("Idle duration", idleDuration.ns());
 
     TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime;
 
@@ -522,15 +531,34 @@
     Duration totalDuration = mFrameDelayDuration +
             std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
             mCommitStartTimes[0];
+    Duration totalDurationWithoutGpu =
+            mFrameDelayDuration + estimatedHwcEndTime - mCommitStartTimes[0];
 
     // We finish SurfaceFlinger when post-composition finishes, so add that in here
     Duration flingerDuration =
             estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0];
+    Duration estimatedGpuDuration = firstGpuTimeline.has_value()
+            ? estimatedGpuEndTime.value_or(TimePoint{0ns}) - firstGpuTimeline->startTime
+            : Duration::fromNs(0);
 
     // Combine the two timings into a single normalized one
     Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration);
+    Duration cpuDuration = combineTimingEstimates(totalDurationWithoutGpu, flingerDuration);
 
-    return std::make_optional(combinedDuration);
+    WorkDuration duration{
+            .timeStampNanos = TimePoint::now().ns(),
+            .durationNanos = combinedDuration.ns(),
+            .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+            .cpuDurationNanos = FlagManager::getInstance().adpf_gpu_sf() ? cpuDuration.ns() : 0,
+            .gpuDurationNanos =
+                    FlagManager::getInstance().adpf_gpu_sf() ? estimatedGpuDuration.ns() : 0,
+    };
+    if (sTraceHintSessionData) {
+        ATRACE_INT64("Idle duration", idleDuration.ns());
+        ATRACE_INT64("Total duration", totalDuration.ns());
+        ATRACE_INT64("Flinger duration", flingerDuration.ns());
+    }
+    return std::make_optional(duration);
 }
 
 Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) {
@@ -581,7 +609,7 @@
 
 std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
         std::optional<TimePoint> previousEndTime) {
-    if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
+    if (!(requiresRenderEngine && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
         return std::nullopt;
     }
     const TimePoint latestGpuStartTime =
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 13e1263..60967b0 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -68,6 +68,8 @@
     virtual void enablePowerHintSession(bool enabled) = 0;
     // Initializes the power hint session
     virtual bool startPowerHintSession(std::vector<int32_t>&& threadIds) = 0;
+    // Provides PowerAdvisor with gpu start time
+    virtual void setGpuStartTime(DisplayId displayId, TimePoint startTime) = 0;
     // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
     virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
     // Reports the start and end times of a hwc validate call this frame for a given display
@@ -80,9 +82,8 @@
     virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0;
     // Reports the most recent present fence time and end time once known
     virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0;
-    // Reports whether a display used client composition this frame
-    virtual void setRequiresClientComposition(DisplayId displayId,
-                                              bool requiresClientComposition) = 0;
+    // Reports whether a display requires RenderEngine to draw
+    virtual void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) = 0;
     // Reports whether a given display skipped validation this frame
     virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0;
     // Reports when a hwc present is delayed, and the time that it will resume
@@ -125,13 +126,14 @@
     void reportActualWorkDuration() override;
     void enablePowerHintSession(bool enabled) override;
     bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+    void setGpuStartTime(DisplayId displayId, TimePoint startTime) override;
     void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) override;
     void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
                               TimePoint validateEndTime) override;
     void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
                              TimePoint presentEndTime) override;
     void setSkippedValidate(DisplayId displayId, bool skipped) override;
-    void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override;
+    void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine);
     void setExpectedPresentTime(TimePoint expectedPresentTime) override;
     void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override;
     void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override;
@@ -192,7 +194,7 @@
         std::optional<TimePoint> hwcValidateStartTime;
         std::optional<TimePoint> hwcValidateEndTime;
         std::optional<TimePoint> hwcPresentDelayedTime;
-        bool usedClientComposition = false;
+        bool requiresRenderEngine = false;
         bool skippedValidate = false;
         // Calculate high-level timing milestones from more granular display timing data
         DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime);
@@ -224,8 +226,8 @@
     // Filter and sort the display ids by a given property
     std::vector<DisplayId> getOrderedDisplayIds(
             std::optional<TimePoint> DisplayTimingData::*sortBy);
-    // Estimates a frame's total work duration including gpu time.
-    std::optional<Duration> estimateWorkDuration();
+    // Estimates a frame's total work duration including gpu and gpu time.
+    std::optional<aidl::android::hardware::power::WorkDuration> estimateWorkDuration();
     // There are two different targets and actual work durations we care about,
     // this normalizes them together and takes the max of the two
     Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
@@ -235,7 +237,6 @@
 
     bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
     std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
-
     // Current frame's delay
     Duration mFrameDelayDuration{0ns};
     // Last frame's post-composition duration
@@ -272,7 +273,6 @@
     std::vector<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
     // The latest values we have received for target and actual
     Duration mTargetDuration = kDefaultTargetDuration;
-    std::optional<Duration> mActualDuration;
     // The list of thread ids, stored so we can restart the session from this class if needed
     std::vector<int32_t> mHintSessionThreadIds;
     Duration mLastTargetDurationSent = kDefaultTargetDuration;
diff --git a/services/surfaceflinger/FrameTracker.cpp b/services/surfaceflinger/FrameTracker.cpp
index 178c531..ca8cdc3 100644
--- a/services/surfaceflinger/FrameTracker.cpp
+++ b/services/surfaceflinger/FrameTracker.cpp
@@ -18,9 +18,6 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
-// This is needed for stdint.h to define INT64_MAX in C++
-#define __STDC_LIMIT_MACROS
-
 #include <inttypes.h>
 
 #include <android-base/stringprintf.h>
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 736fec6..cd2120e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include "TransactionCallbackInvoker.h"
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -150,7 +151,6 @@
         mWindowType(static_cast<WindowInfo::Type>(
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
         mLayerCreationFlags(args.flags),
-        mBorderEnabled(false),
         mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
     ALOGV("Creating Layer %s", getDebugName());
 
@@ -1235,28 +1235,6 @@
     return StretchEffect{};
 }
 
-bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) {
-    if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) {
-        return false;
-    }
-    mBorderEnabled = shouldEnable;
-    mBorderWidth = width;
-    mBorderColor = color;
-    return true;
-}
-
-bool Layer::isBorderEnabled() {
-    return mBorderEnabled;
-}
-
-float Layer::getBorderWidth() {
-    return mBorderWidth;
-}
-
-const half4& Layer::getBorderColor() {
-    return mBorderColor;
-}
-
 bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
                                            bool* transactionNeeded) {
     // Gets the frame rate to propagate to children.
@@ -2912,9 +2890,7 @@
                               currentMaxAcquiredBufferCount);
 }
 
-void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
-                             ui::LayerStack layerStack,
-                             std::function<FenceResult(FenceResult)>&& continuation) {
+sp<CallbackHandle> Layer::findCallbackHandle() {
     // If we are displayed on multiple displays in a single composition cycle then we would
     // need to do careful tracking to enable the use of the mLastClientCompositionFence.
     //  For example we can only use it if all the displays are client comp, and we need
@@ -2949,6 +2925,53 @@
             break;
         }
     }
+    return ch;
+}
+
+void Layer::prepareReleaseCallbacks(ftl::Future<FenceResult> futureFenceResult,
+                                    ui::LayerStack layerStack) {
+    sp<CallbackHandle> ch = findCallbackHandle();
+
+    if (ch != nullptr) {
+        ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+        ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
+        ch->name = mName;
+    } else {
+        // If we didn't get a release callback yet, e.g. some scenarios when capturing
+        // screenshots asynchronously, then make sure we don't drop the fence.
+        mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult));
+        std::vector<ftl::Future<FenceResult>> mergedFences;
+        sp<Fence> prevFence = nullptr;
+        // For a layer that's frequently screenshotted, try to merge fences to make sure we
+        // don't grow unbounded.
+        for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) {
+            auto result = futureReleaseFence.wait_for(0s);
+            if (result != std::future_status::ready) {
+                mergedFences.emplace_back(std::move(futureReleaseFence));
+                continue;
+            }
+            mergeFence(getDebugName(), futureReleaseFence.get().value_or(Fence::NO_FENCE),
+                       prevFence);
+        }
+        if (prevFence != nullptr) {
+            mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))));
+        }
+        mAdditionalPreviousReleaseFences.swap(mergedFences);
+    }
+
+    if (mBufferInfo.mBuffer) {
+        mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    }
+
+    if (mDrawingState.frameNumber > 0) {
+        mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+    }
+}
+
+void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+                             ui::LayerStack layerStack,
+                             std::function<FenceResult(FenceResult)>&& continuation) {
+    sp<CallbackHandle> ch = findCallbackHandle();
 
     if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
         futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
@@ -2956,32 +2979,32 @@
 
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
-        ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
+        ch->previousSharedReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
     } else if (FlagManager::getInstance().screenshot_fence_preservation()) {
         // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
         // asynchronously, then make sure we don't drop the fence.
-        mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult),
-                                                      std::move(continuation));
+        mPreviousReleaseFenceAndContinuations.emplace_back(std::move(futureFenceResult),
+                                                           std::move(continuation));
         std::vector<FenceAndContinuation> mergedFences;
         sp<Fence> prevFence = nullptr;
         // For a layer that's frequently screenshotted, try to merge fences to make sure we don't
         // grow unbounded.
-        for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
-            auto result = futureAndContinution.future.wait_for(0s);
+        for (const auto& futureAndContinuation : mPreviousReleaseFenceAndContinuations) {
+            auto result = futureAndContinuation.future.wait_for(0s);
             if (result != std::future_status::ready) {
-                mergedFences.emplace_back(futureAndContinution);
+                mergedFences.emplace_back(futureAndContinuation);
                 continue;
             }
 
-            mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE),
-                       prevFence);
+            mergeFence(getDebugName(),
+                       futureAndContinuation.chain().get().value_or(Fence::NO_FENCE), prevFence);
         }
         if (prevFence != nullptr) {
             mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
         }
 
-        mAdditionalPreviousReleaseFences.swap(mergedFences);
+        mPreviousReleaseFenceAndContinuations.swap(mergedFences);
     }
 
     if (mBufferInfo.mBuffer) {
@@ -3481,16 +3504,23 @@
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
             handle->previousFrameNumber = mDrawingState.previousFrameNumber;
-            if (FlagManager::getInstance().screenshot_fence_preservation() &&
+            if (FlagManager::getInstance().ce_fence_promise() &&
                 mPreviousReleaseBufferEndpoint == handle->listener) {
                 // Add fences from previous screenshots now so that they can be dispatched to the
                 // client.
-                for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
-                    handle->previousReleaseFences.emplace_back(futureAndContinution.chain());
+                for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) {
+                    handle->previousReleaseFences.emplace_back(std::move(futureReleaseFence));
                 }
                 mAdditionalPreviousReleaseFences.clear();
+            } else if (FlagManager::getInstance().screenshot_fence_preservation() &&
+                       mPreviousReleaseBufferEndpoint == handle->listener) {
+                // Add fences from previous screenshots now so that they can be dispatched to the
+                // client.
+                for (const auto& futureAndContinution : mPreviousReleaseFenceAndContinuations) {
+                    handle->previousSharedReleaseFences.emplace_back(futureAndContinution.chain());
+                }
+                mPreviousReleaseFenceAndContinuations.clear();
             }
-
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0ceecec..d283d6f 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -559,6 +559,14 @@
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack,
                           std::function<FenceResult(FenceResult)>&& continuation = nullptr);
 
+    // Tracks mLastClientCompositionFence and gets the callback handle for this layer.
+    sp<CallbackHandle> findCallbackHandle();
+
+    // Adds the future release fence to a list of fences that are used to release the
+    // last presented buffer. Also keeps track of the layerstack in a list of previous
+    // layerstacks that have been presented.
+    void prepareReleaseCallbacks(ftl::Future<FenceResult>, ui::LayerStack layerStack);
+
     void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
         mClearClientCompositionFenceOnLayerDisplayed = false;
@@ -874,10 +882,6 @@
 
     bool setStretchEffect(const StretchEffect& effect);
     StretchEffect getStretchEffect() const;
-    bool enableBorder(bool shouldEnable, float width, const half4& color);
-    bool isBorderEnabled();
-    float getBorderWidth();
-    const half4& getBorderColor();
 
     bool setBufferCrop(const Rect& /* bufferCrop */);
     bool setDestinationFrame(const Rect& /* destinationFrame */);
@@ -934,6 +938,7 @@
     // the release fences from the correct displays when we release the last buffer
     // from the layer.
     std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+
     struct FenceAndContinuation {
         ftl::SharedFuture<FenceResult> future;
         std::function<FenceResult(FenceResult)> continuation;
@@ -946,7 +951,16 @@
             }
         }
     };
-    std::vector<FenceAndContinuation> mAdditionalPreviousReleaseFences;
+    std::vector<FenceAndContinuation> mPreviousReleaseFenceAndContinuations;
+
+    // Release fences for buffers that have not yet received a release
+    // callback. A release callback may not be given when capturing
+    // screenshots asynchronously. There may be no buffer update for the
+    // layer, but the layer will still be composited on the screen in every
+    // frame. Kepping track of these fences ensures that they are not dropped
+    // and can be dispatched to the client at a later time.
+    std::vector<ftl::Future<FenceResult>> mAdditionalPreviousReleaseFences;
+
     // Exposed so SurfaceFlinger can assert that it's held
     const sp<SurfaceFlinger> mFlinger;
 
@@ -1238,10 +1252,6 @@
 
     bool findInHierarchy(const sp<Layer>&);
 
-    bool mBorderEnabled = false;
-    float mBorderWidth;
-    half4 mBorderColor;
-
     void setTransformHintLegacy(ui::Transform::RotationFlags);
     void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 43a4397..620edca 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -27,6 +27,8 @@
 
 #include "LayerFE.h"
 #include "SurfaceFlinger.h"
+#include "ui/FenceResult.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -387,4 +389,29 @@
     return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
 }
 
+void LayerFE::setReleaseFence(const FenceResult& releaseFence) {
+    // Promises should not be fulfilled more than once. This case can occur if virtual
+    // displays with the same layerstack ID are being created and destroyed in quick
+    // succession, such as in tests. This would result in a race condition in which
+    // multiple displays have the same layerstack ID within the same vsync interval.
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) {
+        return;
+    }
+    mReleaseFence.set_value(releaseFence);
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED;
+}
+
+// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched.
+ftl::Future<FenceResult> LayerFE::createReleaseFenceFuture() {
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+        LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled.");
+    }
+    mReleaseFence = std::promise<FenceResult>();
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED;
+    return mReleaseFence.get_future();
+}
+
+LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
+    return mReleaseFencePromiseStatus;
+}
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 66cb88b..019fa22 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -22,6 +22,9 @@
 #include "compositionengine/LayerFE.h"
 #include "compositionengine/LayerFECompositionState.h"
 #include "renderengine/LayerSettings.h"
+#include "ui/LayerStack.h"
+
+#include <ftl/future.h>
 
 namespace android {
 
@@ -47,6 +50,9 @@
     std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
     CompositionResult&& stealCompositionResult();
+    ftl::Future<FenceResult> createReleaseFenceFuture() override;
+    void setReleaseFence(const FenceResult& releaseFence) override;
+    LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -76,6 +82,8 @@
 
     CompositionResult mCompositionResult;
     std::string mName;
+    std::promise<FenceResult> mReleaseFence;
+    ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index b8d5e76..974c837 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -40,14 +40,15 @@
 
 namespace {
 
-bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
+bool isLayerActive(const LayerInfo& info, nsecs_t threshold, bool isVrrDevice) {
     if (FlagManager::getInstance().misc1() && !info.isVisible()) {
         return false;
     }
 
     // Layers with an explicit frame rate or frame rate category are kept active,
     // but ignore NoVote.
-    if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
+    const auto frameRate = info.getSetFrameRateVote();
+    if (frameRate.isValid() && !frameRate.isNoVote() && frameRate.isVoteValidForMrr(isVrrDevice)) {
         return true;
     }
 
@@ -194,7 +195,7 @@
 
     std::lock_guard lock(mLock);
 
-    partitionLayers(now);
+    partitionLayers(now, selector.isVrrDevice());
 
     for (const auto& [key, value] : mActiveLayerInfos) {
         auto& info = value.second;
@@ -236,7 +237,7 @@
     return summary;
 }
 
-void LayerHistory::partitionLayers(nsecs_t now) {
+void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
     ATRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
@@ -244,7 +245,7 @@
     LayerInfos::iterator it = mInactiveLayerInfos.begin();
     while (it != mInactiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // move this to the active map
 
             mActiveLayerInfos.insert({it->first, std::move(it->second)});
@@ -262,7 +263,7 @@
     it = mActiveLayerInfos.begin();
     while (it != mActiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
 
@@ -305,7 +306,7 @@
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameModeFrameRateOverride.getIntValue());
                     }
-                } else if (frameRate.isValid()) {
+                } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
                     info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
                     if (CC_UNLIKELY(mTraceEnabled)) {
@@ -321,14 +322,30 @@
                               gameDefaultFrameRateOverride.getIntValue());
                     }
                 } else {
+                    if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                              "%s %s",
+                                              info->getName().c_str(),
+                                              ftl::enum_string(frameRate.vote.type).c_str(),
+                                              to_string(frameRate.vote.rate).c_str(),
+                                              ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             } else {
-                if (frameRate.isValid()) {
+                if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
                     const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
                     info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
                                         frameRate.category});
                 } else {
+                    if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                              "%s %s",
+                                              info->getName().c_str(),
+                                              ftl::enum_string(frameRate.vote.type).c_str(),
+                                              to_string(frameRate.vote.rate).c_str(),
+                                              ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             }
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index a6f1b56..c09f148 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -111,9 +111,12 @@
     std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock);
 
     // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
-    // layers to mInactiveLayerInfos.
+    // layers to mInactiveLayerInfos. Layer's active state is determined by multiple factors
+    // such as update activity, visibility, and frame rate vote.
     // worst case time complexity is O(2 * inactive + active)
-    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+    // now: the current time (system time) when calling the method
+    // isVrrDevice: true if the device has DisplayMode with VrrConfig specified.
+    void partitionLayers(nsecs_t now, bool isVrrDevice) REQUIRES(mLock);
 
     enum class LayerStatus {
         NotFound,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 9745452..1bc4ac2 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -566,6 +566,18 @@
     return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
 }
 
+bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const {
+    if (isVrrDevice || FlagManager::getInstance().frame_rate_category_mrr()) {
+        return true;
+    }
+
+    if (category == FrameRateCategory::Default && vote.type != FrameRateCompatibility::Gte) {
+        return true;
+    }
+
+    return false;
+}
+
 std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
     return stream << "{rate=" << rate.vote.rate << " type=" << ftl::enum_string(rate.vote.type)
                   << " seamlessness=" << ftl::enum_string(rate.vote.seamlessness) << '}';
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 326e444..40903ed 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -146,6 +146,10 @@
         // selection.
         bool isNoVote() const;
 
+        // Checks whether the given FrameRate's vote specifications is valid for MRR devices
+        // given the current flagging.
+        bool isVoteValidForMrr(bool isVrrDevice) const;
+
     private:
         static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
             if (!rate.isValid()) {
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index cd45bfd..7e61dc0 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -115,9 +115,24 @@
             break;
         }
 
-        auto triggerTime = mClock->now() + mInterval;
+        auto triggerTime = mClock->now() + mInterval.load();
         state = TimerState::WAITING;
         while (true) {
+            if (mPaused) {
+                mWaiting = true;
+                int result = sem_wait(&mSemaphore);
+                if (result && errno != EINTR) {
+                    std::stringstream ss;
+                    ss << "sem_wait failed (" << errno << ")";
+                    LOG_ALWAYS_FATAL("%s", ss.str().c_str());
+                }
+
+                mWaiting = false;
+                state = checkForResetAndStop(state);
+                if (state == TimerState::STOPPED) {
+                    break;
+                }
+            }
             // Wait until triggerTime time to check if we need to reset or drop into the idle state.
             if (const auto triggerInterval = triggerTime - mClock->now(); triggerInterval > 0ns) {
                 mWaiting = true;
@@ -137,14 +152,14 @@
                 break;
             }
 
-            if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
+            if (!mPaused && state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
                 triggerTimeout = true;
                 state = TimerState::IDLE;
                 break;
             }
 
             if (state == TimerState::RESET) {
-                triggerTime = mLastResetTime.load() + mInterval;
+                triggerTime = mLastResetTime.load() + mInterval.load();
                 state = TimerState::WAITING;
             }
         }
@@ -179,5 +194,15 @@
     }
 }
 
+void OneShotTimer::pause() {
+    mPaused = true;
+}
+
+void OneShotTimer::resume() {
+    if (mPaused.exchange(false)) {
+        LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed");
+    }
+}
+
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index 02e8719..4e1e2ee 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -43,7 +43,8 @@
                  std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>());
     ~OneShotTimer();
 
-    Duration interval() const { return mInterval; }
+    Duration interval() const { return mInterval.load(); }
+    void setInterval(Interval value) { mInterval = value; }
 
     // Initializes and turns on the idle timer.
     void start();
@@ -51,6 +52,10 @@
     void stop();
     // Resets the wakeup time and fires the reset callback.
     void reset();
+    // Pauses the timer. reset calls will get ignored.
+    void pause();
+    // Resumes the timer.
+    void resume();
 
 private:
     // Enum to track in what state is the timer.
@@ -91,7 +96,7 @@
     std::string mName;
 
     // Interval after which timer expires.
-    const Interval mInterval;
+    std::atomic<Interval> mInterval;
 
     // Callback that happens when timer resets.
     const ResetCallback mResetCallback;
@@ -105,6 +110,7 @@
     std::atomic<bool> mResetTriggered = false;
     std::atomic<bool> mStopTriggered = false;
     std::atomic<bool> mWaiting = false;
+    std::atomic<bool> mPaused = false;
     std::atomic<std::chrono::steady_clock::time_point> mLastResetTime;
 };
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 56c29e2..a37fb96 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -285,11 +285,12 @@
 
 std::string RefreshRateSelector::Policy::toString() const {
     return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
-                              ", primaryRanges=%s, appRequestRanges=%s}",
+                              ", primaryRanges=%s, appRequestRanges=%s idleScreenConfig=%s}",
                               ftl::to_underlying(defaultMode),
                               allowGroupSwitching ? "true" : "false",
-                              to_string(primaryRanges).c_str(),
-                              to_string(appRequestRanges).c_str());
+                              to_string(primaryRanges).c_str(), to_string(appRequestRanges).c_str(),
+                              idleScreenConfigOpt ? idleScreenConfigOpt->toString().c_str()
+                                                  : "nullptr");
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
@@ -474,21 +475,23 @@
 }
 
 auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                                              GlobalSignals signals) const -> RankedFrameRates {
+                                              GlobalSignals signals, Fps pacesetterFps) const
+        -> RankedFrameRates {
+    GetRankedFrameRatesCache cache{layers, signals, pacesetterFps};
+
     std::lock_guard lock(mLock);
 
-    if (mGetRankedFrameRatesCache &&
-        mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+    if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) {
         return mGetRankedFrameRatesCache->result;
     }
 
-    const auto result = getRankedFrameRatesLocked(layers, signals);
-    mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
-    return result;
+    cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps);
+    mGetRankedFrameRatesCache = std::move(cache);
+    return mGetRankedFrameRatesCache->result;
 }
 
 auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                                    GlobalSignals signals) const
+                                                    GlobalSignals signals, Fps pacesetterFps) const
         -> RankedFrameRates {
     using namespace fps_approx_ops;
     ATRACE_CALL();
@@ -496,6 +499,24 @@
 
     const auto& activeMode = *getActiveModeLocked().modePtr;
 
+    if (pacesetterFps.isValid()) {
+        ALOGV("Follower display");
+
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending,
+                                            std::nullopt, [&](FrameRateMode mode) {
+                                                return mode.modePtr->getPeakFps() == pacesetterFps;
+                                            });
+
+        if (!ranking.empty()) {
+            ATRACE_FORMAT_INSTANT("%s (Follower display)",
+                                  to_string(ranking.front().frameRateMode.fps).c_str());
+
+            return {ranking, kNoSignals, pacesetterFps};
+        }
+
+        ALOGW("Follower display cannot follow the pacesetter");
+    }
+
     // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
@@ -841,7 +862,7 @@
     // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
-    const bool touchBoostForExplicitExact = [&] {
+    const auto isTouchBoostForExplicitExact = [&]() -> bool {
         if (supportsAppFrameRateOverrideByContent()) {
             // Enable touch boost if there are other layers besides exact
             return explicitExact + noVoteLayers + explicitGteLayers != layers.size();
@@ -849,13 +870,11 @@
             // Enable touch boost if there are no exact layers
             return explicitExact == 0;
         }
-    }();
+    };
 
-    const bool touchBoostForCategory =
-            explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size();
-
-    const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-    using fps_approx_ops::operator<;
+    const auto isTouchBoostForCategory = [&]() -> bool {
+        return explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size();
+    };
 
     // A method for UI Toolkit to send the touch signal via "HighHint" category vote,
     // which will touch boost when there are no ExplicitDefault layer votes. This is an
@@ -863,13 +882,17 @@
     // compatibility to limit the frame rate, which should not have touch boost.
     const bool hasInteraction = signals.touch || interactiveLayers > 0;
 
-    if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        touchBoostForCategory &&
-        scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
-        ALOGV("Touch Boost");
-        ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
-                              to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && isTouchBoostForExplicitExact() &&
+        isTouchBoostForCategory()) {
+        const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+        using fps_approx_ops::operator<;
+
+        if (scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
+            ALOGV("Touch Boost");
+            ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+                                  to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
+            return {touchRefreshRates, GlobalSignals{.touch = true}};
+        }
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
@@ -883,8 +906,8 @@
         return {ascendingWithPreferred, kNoSignals};
     }
 
-    ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
-    ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
+    ALOGV("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
+    ATRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
@@ -1226,19 +1249,21 @@
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
 
     mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
+    mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
+            activeModeOpt->get()->getVrrConfig().has_value();
 }
 
 RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
                                          Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
-    initializeIdleTimer();
+    initializeIdleTimer(mConfig.legacyIdleTimerTimeout);
     FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
 }
 
-void RefreshRateSelector::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeout > 0ms) {
+void RefreshRateSelector::initializeIdleTimer(std::chrono::milliseconds timeout) {
+    if (timeout > 0ms) {
         mIdleTimer.emplace(
-                "IdleTimer", mConfig.idleTimerTimeout,
+                "IdleTimer", timeout,
                 [this] {
                     std::scoped_lock lock(mIdleTimerCallbacksMutex);
                     if (const auto callbacks = getIdleTimerCallbacks()) {
@@ -1361,9 +1386,40 @@
 
         mGetRankedFrameRatesCache.reset();
 
-        if (*getCurrentPolicyLocked() == oldPolicy) {
+        const auto& idleScreenConfigOpt = getCurrentPolicyLocked()->idleScreenConfigOpt;
+        if (idleScreenConfigOpt != oldPolicy.idleScreenConfigOpt) {
+            if (!idleScreenConfigOpt.has_value()) {
+                // fallback to legacy timer if existed, otherwise pause the old timer
+                LOG_ALWAYS_FATAL_IF(!mIdleTimer);
+                if (mConfig.legacyIdleTimerTimeout > 0ms) {
+                    mIdleTimer->setInterval(mConfig.legacyIdleTimerTimeout);
+                    mIdleTimer->resume();
+                } else {
+                    mIdleTimer->pause();
+                }
+            } else if (idleScreenConfigOpt->timeoutMillis > 0) {
+                // create a new timer or reconfigure
+                const auto timeout = std::chrono::milliseconds{idleScreenConfigOpt->timeoutMillis};
+                if (!mIdleTimer) {
+                    initializeIdleTimer(timeout);
+                    if (mIdleTimerStarted) {
+                        mIdleTimer->start();
+                    }
+                } else {
+                    mIdleTimer->setInterval(timeout);
+                    mIdleTimer->resume();
+                }
+            } else {
+                if (mIdleTimer) {
+                    mIdleTimer->pause();
+                }
+            }
+        }
+
+        if (getCurrentPolicyLocked()->similarExceptIdleConfig(oldPolicy)) {
             return SetPolicyResult::Unchanged;
         }
+
         constructAvailableRefreshRates();
 
         displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
@@ -1439,7 +1495,8 @@
             }
             return str;
         };
-        ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
+        ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(),
+              mIsVrrDevice);
 
         return frameRateModes;
     };
@@ -1448,6 +1505,11 @@
     mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
 }
 
+bool RefreshRateSelector::isVrrDevice() const {
+    std::lock_guard lock(mLock);
+    return mIsVrrDevice;
+}
+
 Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
     using namespace fps_approx_ops;
 
@@ -1559,7 +1621,10 @@
 }
 
 std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
-    return mConfig.idleTimerTimeout;
+    if (FlagManager::getInstance().idle_screen_refresh_rate_timeout() && mIdleTimer) {
+        return std::chrono::duration_cast<std::chrono::milliseconds>(mIdleTimer->interval());
+    }
+    return mConfig.legacyIdleTimerTimeout;
 }
 
 // TODO(b/293651105): Extract category FpsRange mapping to OEM-configurable config.
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 6051e89..4f491d9 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -67,26 +67,32 @@
         FpsRanges primaryRanges;
         // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
         FpsRanges appRequestRanges;
+        // The idle timer configuration, if provided.
+        std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig> idleScreenConfigOpt;
 
         Policy() = default;
 
         Policy(DisplayModeId defaultMode, FpsRange range,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
-                       allowGroupSwitching) {}
+                       allowGroupSwitching, idleScreenConfigOpt) {}
 
         Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRanges(primaryRanges),
-                appRequestRanges(appRequestRanges) {}
+                appRequestRanges(appRequestRanges),
+                idleScreenConfigOpt(idleScreenConfigOpt) {}
 
         bool operator==(const Policy& other) const {
             using namespace fps_approx_ops;
-            return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges &&
-                    appRequestRanges == other.appRequestRanges &&
-                    allowGroupSwitching == other.allowGroupSwitching;
+            return similarExceptIdleConfig(other) &&
+                    idleScreenConfigOpt == other.idleScreenConfigOpt;
         }
 
         bool operator!=(const Policy& other) const { return !(*this == other); }
@@ -95,6 +101,13 @@
             return isApproxEqual(primaryRanges.physical.min, primaryRanges.physical.max);
         }
 
+        bool similarExceptIdleConfig(const Policy& updated) const {
+            using namespace fps_approx_ops;
+            return defaultMode == updated.defaultMode && primaryRanges == updated.primaryRanges &&
+                    appRequestRanges == updated.appRequestRanges &&
+                    allowGroupSwitching == updated.allowGroupSwitching;
+        }
+
         std::string toString() const;
     };
 
@@ -233,14 +246,18 @@
     struct RankedFrameRates {
         FrameRateRanking ranking; // Ordered by descending score.
         GlobalSignals consideredSignals;
+        Fps pacesetterFps;
 
         bool operator==(const RankedFrameRates& other) const {
-            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+            return ranking == other.ranking && consideredSignals == other.consideredSignals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
         }
     };
 
-    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
-            EXCLUDES(mLock);
+    // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
+    // that refresh rate.
+    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
+                                         Fps pacesetterFps = {}) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -287,7 +304,7 @@
         int frameRateMultipleThreshold = 0;
 
         // The Idle Timer timeout. 0 timeout means no idle timer.
-        std::chrono::milliseconds idleTimerTimeout = 0ms;
+        std::chrono::milliseconds legacyIdleTimerTimeout = 0ms;
 
         // The controller representing how the kernel idle timer will be configured
         // either on the HWC api or sysprop.
@@ -298,7 +315,7 @@
             DisplayModes, DisplayModeId activeModeId,
             Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
                              .frameRateMultipleThreshold = 0,
-                             .idleTimerTimeout = 0ms,
+                             .legacyIdleTimerTimeout = 0ms,
                              .kernelIdleTimerController = {}});
 
     RefreshRateSelector(const RefreshRateSelector&) = delete;
@@ -379,12 +396,14 @@
     }
 
     void startIdleTimer() {
+        mIdleTimerStarted = true;
         if (mIdleTimer) {
             mIdleTimer->start();
         }
     }
 
     void stopIdleTimer() {
+        mIdleTimerStarted = false;
         if (mIdleTimer) {
             mIdleTimer->stop();
         }
@@ -406,6 +425,8 @@
 
     std::chrono::milliseconds getIdleTimerTimeout();
 
+    bool isVrrDevice() const;
+
 private:
     friend struct TestableRefreshRateSelector;
 
@@ -415,7 +436,8 @@
     const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
 
     RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const REQUIRES(mLock);
+                                               GlobalSignals signals, Fps pacesetterFps) const
+            REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -474,7 +496,7 @@
     void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
             REQUIRES(kMainThreadContext);
 
-    void initializeIdleTimer();
+    void initializeIdleTimer(std::chrono::milliseconds timeout);
 
     std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
             REQUIRES(mIdleTimerCallbacksMutex) {
@@ -513,6 +535,9 @@
     std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
+    // Caches whether the device is VRR-compatible based on the active display mode.
+    bool mIsVrrDevice GUARDED_BY(mLock) = false;
+
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
@@ -534,8 +559,16 @@
     Config::FrameRateOverride mFrameRateOverrideConfig;
 
     struct GetRankedFrameRatesCache {
-        std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+        std::vector<LayerRequirement> layers;
+        GlobalSignals signals;
+        Fps pacesetterFps;
+
         RankedFrameRates result;
+
+        bool matches(const GetRankedFrameRatesCache& other) const {
+            return layers == other.layers && signals == other.signals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
+        }
     };
     mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
 
@@ -544,6 +577,7 @@
     std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
     // Used to detect (lack of) frame activity.
     ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
+    std::atomic<bool> mIdleTimerStarted = false;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index d92edb8..7968096 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -306,8 +306,11 @@
         const auto pacesetterOpt = pacesetterDisplayLocked();
         LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
         const Display& pacesetter = *pacesetterOpt;
-        return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
-                              pacesetter.schedulePtr->period());
+        const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode();
+        const auto refreshRate = frameRateMode.fps;
+        const auto displayVsync = frameRateMode.modePtr->getVsyncRate();
+        const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate);
+        return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period());
     }();
 
     const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
@@ -1146,38 +1149,31 @@
 auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
-    ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
+    DisplayModeChoiceMap modeChoices;
     const auto globalSignals = makeGlobalSignals();
-    Fps pacesetterFps;
+
+    const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
+        auto rankedFrameRates =
+                pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
+                                                                   globalSignals);
+
+        const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
+
+        modeChoices.try_emplace(*mPacesetterDisplayId,
+                                DisplayModeChoice::from(std::move(rankedFrameRates)));
+        return pacesetterFps;
+    }();
 
     for (const auto& [id, display] : mDisplays) {
+        if (id == *mPacesetterDisplayId) continue;
+
         auto rankedFrameRates =
-                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
-                                                         globalSignals);
-        if (id == *mPacesetterDisplayId) {
-            pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
-        }
-        perDisplayRanking.push_back(std::move(rankedFrameRates));
+                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
+                                                         pacesetterFps);
+
+        modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
     }
 
-    DisplayModeChoiceMap modeChoices;
-    using fps_approx_ops::operator==;
-
-    for (auto& [rankings, signals] : perDisplayRanking) {
-        const auto chosenFrameRateMode =
-                ftl::find_if(rankings,
-                             [&](const auto& ranking) {
-                                 return ranking.frameRateMode.fps == pacesetterFps;
-                             })
-                        .transform([](const auto& scoredFrameRate) {
-                            return scoredFrameRate.get().frameRateMode;
-                        })
-                        .value_or(rankings.front().frameRateMode);
-
-        modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
-                                DisplayModeChoice{chosenFrameRateMode, signals});
-    }
     return modeChoices;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 494a91b..ccb3aa7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -402,6 +402,11 @@
         DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
               : mode(std::move(mode)), consideredSignals(consideredSignals) {}
 
+        static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) {
+            return {rankedFrameRates.ranking.front().frameRateMode,
+                    rankedFrameRates.consideredSignals};
+        }
+
         FrameRateMode mode;
         GlobalSignals consideredSignals;
 
@@ -437,6 +442,7 @@
 
     // IEventThreadCallback overrides
     bool throttleVsync(TimePoint, uid_t) override;
+    // Get frame interval
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index db1930d..092bc0d 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -377,15 +377,20 @@
     mRenderRateOpt = renderRate;
     const auto renderPeriodDelta =
             prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
-    const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
-            mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
     if (applyImmediately) {
+        ATRACE_FORMAT_INSTANT("applyImmediately");
         while (mTimelines.size() > 1) {
             mTimelines.pop_front();
         }
 
         mTimelines.front().setRenderRate(renderRate);
-    } else if (newRenderRateIsHigher) {
+        return;
+    }
+
+    const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
+            mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
+    if (newRenderRateIsHigher) {
+        ATRACE_FORMAT_INSTANT("newRenderRateIsHigher");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
 
@@ -548,10 +553,23 @@
         mLastTimestampIndex = 0;
     }
 
-    mTimelines.clear();
-    mLastCommittedVsync = TimePoint::fromNs(0);
     mIdealPeriod = Period::fromNs(idealPeriod());
-    mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+    if (mTimelines.empty()) {
+        mLastCommittedVsync = TimePoint::fromNs(0);
+        mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+    } else {
+        while (mTimelines.size() > 1) {
+            mTimelines.pop_front();
+        }
+        mTimelines.front().setRenderRate(mRenderRateOpt);
+        // set mLastCommittedVsync to a valid vsync but don't commit too much in the future
+        const auto vsyncOpt = mTimelines.front().nextAnticipatedVSyncTimeFrom(
+            getVSyncPredictionModelLocked(),
+            /* minFramePeriodOpt */ std::nullopt,
+            snapToVsync(mClock->now()), MissedVsync{},
+            /* lastVsyncOpt */ std::nullopt);
+        mLastCommittedVsync = *vsyncOpt;
+    }
 }
 
 bool VSyncPredictor::needsMoreSamples() const {
@@ -583,6 +601,7 @@
     if (mRenderRateOpt &&
         mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
                 mClock->now()) {
+        ATRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
         mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
@@ -630,33 +649,30 @@
     const auto threshold = model.slope / 2;
     const auto lastFrameMissed =
             lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
-    nsecs_t vsyncFixupTime = 0;
     if (FlagManager::getInstance().vrr_config() && lastFrameMissed) {
         // If the last frame missed is the last vsync, we already shifted the timeline. Depends on
         // whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a different
         // fixup. There is no need to to shift the vsync timeline again.
         vsyncTime += missedVsync.fixup.ns();
         ATRACE_FORMAT_INSTANT("lastFrameMissed");
-    } else if (minFramePeriodOpt) {
-        if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) {
-            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
-            // first before trying to use it.
-            if (mLastVsyncSequence->seq > 0) {
-                lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
-            }
-            const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
-            if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
-                vsyncFixupTime = *lastVsyncOpt + minFramePeriodOpt->ns() - vsyncTime;
-                ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f "
-                                      "from "
-                                      "prev. "
-                                      "adjust by %.2f",
-                                      static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
-                                      static_cast<float>(vsyncTime - *lastVsyncOpt) / 1e6f,
-                                      static_cast<float>(vsyncFixupTime) / 1e6f);
-            }
+    } else if (FlagManager::getInstance().vrr_config() && minFramePeriodOpt && mRenderRateOpt &&
+               lastVsyncOpt) {
+        // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
+        // first before trying to use it.
+        lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+        const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
+        if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
+            // avoid a duplicate vsync
+            ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f which "
+                                  "is %.2f "
+                                  "from "
+                                  "prev. "
+                                  "adjust by %.2f",
+                                  static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+                                  static_cast<float>(vsyncDiff) / 1e6f,
+                                  static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
+            vsyncTime += mRenderRateOpt->getPeriodNsecs();
         }
-        vsyncTime += vsyncFixupTime;
     }
 
     ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
@@ -666,12 +682,6 @@
         return std::nullopt;
     }
 
-    // If we needed a fixup, it means that we changed the render rate and the chosen vsync would
-    // cross minFramePeriod. In that case we need to shift the entire vsync timeline.
-    if (vsyncFixupTime > 0) {
-        shiftVsyncSequence(Duration::fromNs(vsyncFixupTime));
-    }
-
     return TimePoint::fromNs(vsyncTime);
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 3ed1d41..a661292 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -83,7 +83,7 @@
     };
 
     struct MissedVsync {
-        TimePoint vsync;
+        TimePoint vsync = TimePoint::fromNs(0);
         Duration fixup = Duration::fromNs(0);
     };
 
@@ -97,7 +97,7 @@
         std::optional<TimePoint> validUntil() const { return mValidUntil; }
         bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
         void shiftVsyncSequence(Duration phase);
-        void setRenderRate(Fps renderRate) { mRenderRateOpt = renderRate; }
+        void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
 
     private:
         nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 186a2d6..9b8f310 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -217,6 +217,11 @@
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     }
 
+    if (mExternalIgnoreFences) {
+      // keep HWVSync on as long as we ignore present fences.
+      mMoreSamplesNeeded = true;
+    }
+
     if (!mMoreSamplesNeeded) {
         setIgnorePresentFencesInternal(false);
     }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index bf210af..734058a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
@@ -167,10 +168,6 @@
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
 
-// To enable layer borders in the system, change the below flag to true.
-#undef DOES_CONTAIN_BORDER
-#define DOES_CONTAIN_BORDER false
-
 namespace android {
 using namespace std::chrono_literals;
 using namespace std::string_literals;
@@ -578,11 +575,11 @@
 
 sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure,
                                           float requestedRefreshRate) {
-    // onTransact already checks for some permissions, but adding an additional check here.
-    // This is to ensure that only system and graphics can request to create a secure
+    // SurfaceComposerAIDL checks for some permissions, but adding an additional check here.
+    // This is to ensure that only root, system, and graphics can request to create a secure
     // display. Secure displays can show secure content so we add an additional restriction on it.
     const int uid = IPCThreadState::self()->getCallingUid();
-    if (secure && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
+    if (secure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
         ALOGE("Only privileged processes can create a secure display");
         return nullptr;
     }
@@ -2702,27 +2699,14 @@
         mLayerMetadataSnapshotNeeded = false;
     }
 
-    if (DOES_CONTAIN_BORDER) {
-        refreshArgs.borderInfoList.clear();
-        mDrawingState.traverse([&refreshArgs](Layer* layer) {
-            if (layer->isBorderEnabled()) {
-                compositionengine::BorderRenderInfo info;
-                info.width = layer->getBorderWidth();
-                info.color = layer->getBorderColor();
-                layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) {
-                    info.layerIds.push_back(ilayer->getSequence());
-                });
-                refreshArgs.borderInfoList.emplace_back(std::move(info));
-            }
-        });
-    }
-
     refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
 
-    refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
-    for (auto layer : mLayersWithQueuedFrames) {
-        if (auto layerFE = layer->getCompositionEngineLayerFE())
-            refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& layer : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE())
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+        }
     }
 
     refreshArgs.outputColorSetting = mDisplayColorSetting;
@@ -2784,22 +2768,56 @@
     }
 
     refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    for (auto [layer, layerFE] : layers) {
+    for (auto& [layer, layerFE] : layers) {
         layer->onPreComposition(refreshArgs.refreshStartTime);
     }
 
-    mCompositionEngine->present(refreshArgs);
-    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
-
-    for (auto [layer, layerFE] : layers) {
-        CompositionResult compositionResult{layerFE->stealCompositionResult()};
-        for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
-            Layer* clonedFrom = layer->getClonedFrom().get();
-            auto owningLayer = clonedFrom ? clonedFrom : layer;
-            owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            attachReleaseFenceFutureToLayer(layer, layerFE,
+                                            layerFE->mSnapshot->outputFilter.layerStack);
         }
-        if (compositionResult.lastClientCompositionFence) {
-            layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& layer : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+                // Some layers are not displayed and do not yet have a future release fence
+                if (layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::UNINITIALIZED ||
+                    layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::FULFILLED) {
+                    // layerStack is invalid because layer is not on a display
+                    attachReleaseFenceFutureToLayer(layer.get(), layerFE.get(),
+                                                    ui::INVALID_LAYER_STACK);
+                }
+            }
+        }
+
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto& [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
+        }
+
+    } else {
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
+                Layer* clonedFrom = layer->getClonedFrom().get();
+                auto owningLayer = clonedFrom ? clonedFrom : layer;
+                owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+            }
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
         }
     }
 
@@ -3065,8 +3083,13 @@
             auto optDisplay = layerStackToDisplay.get(layerStack);
             if (optDisplay && !optDisplay->get()->isVirtual()) {
                 auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId());
-                layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
-                                        ui::INVALID_LAYER_STACK);
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    layer->prepareReleaseCallbacks(ftl::yield<FenceResult>(fence),
+                                                   ui::INVALID_LAYER_STACK);
+                } else {
+                    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+                                            ui::INVALID_LAYER_STACK);
+                }
             }
         }
         layer->releasePendingBuffer(presentTime.ns());
@@ -3565,7 +3588,7 @@
                 {.enableFrameRateOverride = enableFrameRateOverride,
                  .frameRateMultipleThreshold =
                          base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0),
-                 .idleTimerTimeout = idleTimerTimeoutMs,
+                 .legacyIdleTimerTimeout = idleTimerTimeoutMs,
                  .kernelIdleTimerController = kernelIdleTimerController};
 
         creationArgs.refreshRateSelector =
@@ -5461,11 +5484,6 @@
     if (what & layer_state_t::eBlurRegionsChanged) {
         if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded;
     }
-    if (what & layer_state_t::eRenderBorderChanged) {
-        if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) {
-            flags |= eTraversalNeeded;
-        }
-    }
     if (what & layer_state_t::eLayerStackChanged) {
         ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
         // We only allow setting layer stacks for top level layers,
@@ -6244,9 +6262,9 @@
             {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
             {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
             {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
-            {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
-            {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
-            {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+            {"--latency"s, argsMainThreadDumper(&SurfaceFlinger::dumpStats)},
+            {"--latency-clear"s, argsMainThreadDumper(&SurfaceFlinger::clearStats)},
+            {"--list"s, mainThreadDumper(&SurfaceFlinger::listLayers)},
             {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
             {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
             {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
@@ -6279,28 +6297,29 @@
     return doDump(fd, DumpArgs(), asProto);
 }
 
-void SurfaceFlinger::listLayersLocked(std::string& result) const {
-    mCurrentState.traverseInZOrder(
-            [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getDebugName()); });
+void SurfaceFlinger::listLayers(std::string& result) const {
+    for (const auto& layer : mLayerLifecycleManager.getLayers()) {
+        StringAppendF(&result, "%s\n", layer->getDebugString().c_str());
+    }
 }
 
-void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const {
+void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const {
     StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC());
     if (args.size() < 2) return;
 
     const auto name = String8(args[1]);
-    mCurrentState.traverseInZOrder([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (layer->getName() == name.c_str()) {
             layer->dumpFrameStats(result);
         }
     });
 }
 
-void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) {
+void SurfaceFlinger::clearStats(const DumpArgs& args, std::string&) {
     const bool clearAll = args.size() < 2;
     const auto name = clearAll ? String8() : String8(args[1]);
 
-    mCurrentState.traverse([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (clearAll || layer->getName() == name.c_str()) {
             layer->clearFrameStats();
         }
@@ -7998,7 +8017,7 @@
 
     // really small crop or frameScale
     if (reqSize.width <= 0 || reqSize.height <= 0) {
-        ALOGW("Failed to captureLayes: crop or scale too small");
+        ALOGW("Failed to captureLayers: crop or scale too small");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
@@ -8072,6 +8091,17 @@
                         args.allowProtected, args.grayscale, captureListener);
 }
 
+// Creates a Future release fence for a layer and keeps track of it in a list to
+// release the buffer when the Future is complete. Calls from composittion
+// involve needing to refresh the composition start time for stats.
+void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE,
+                                                     ui::LayerStack layerStack) {
+    ftl::Future<FenceResult> futureFence = layerFE->createReleaseFenceFuture();
+    Layer* clonedFrom = layer->getClonedFrom().get();
+    auto owningLayer = clonedFrom ? clonedFrom : layer;
+    owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
+}
+
 bool SurfaceFlinger::layersHasProtectedLayer(
         const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
     bool protectedLayerFound = false;
@@ -8107,7 +8137,6 @@
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
-        // Snapshots must be taken from the main thread.
         auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
         hasProtectedLayer = layersHasProtectedLayer(layers);
     }
@@ -8148,6 +8177,14 @@
 
     auto takeScreenshotFn = [=, this, renderAreaFuture = std::move(renderAreaFuture)]() REQUIRES(
                                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+        // LayerSnapshots must be obtained from the main thread.
+        auto layers = getLayerSnapshots();
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& [layer, layerFE] : layers) {
+                attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+            }
+        }
+
         ScreenCaptureResults captureResults;
         std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
         if (!renderArea) {
@@ -8161,8 +8198,8 @@
 
         ftl::SharedFuture<FenceResult> renderFuture;
         renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-            renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
-                                            grayscale, isProtected, captureResults);
+            renderFuture = renderScreenImpl(renderArea, buffer, regionSampling, grayscale,
+                                            isProtected, captureResults, layers);
         });
 
         if (captureListener) {
@@ -8179,6 +8216,10 @@
         return renderFuture;
     };
 
+    // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number
+    // of calls on the main thread. renderAreaFuture runs on the main thread and should
+    // no longer be a future, so that it does not need to make an additional jump on the
+    // main thread whenever get() is called.
     auto future =
             mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn)));
 
@@ -8194,9 +8235,17 @@
         std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
-    ATRACE_CALL();
-
     auto layers = getLayerSnapshots();
+    return renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected,
+                            captureResults, layers);
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
+        std::shared_ptr<const RenderArea> renderArea,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
+        std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) {
+    ATRACE_CALL();
 
     for (auto& [_, layerFE] : layers) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -8350,24 +8399,26 @@
     auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
                                                      : ftl::yield(present()).share();
 
-    for (auto& [layer, layerFE] : layers) {
-        layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
-                                [layerFE = std::move(layerFE)](FenceResult) {
-                                    if (FlagManager::getInstance()
-                                                .screenshot_fence_preservation()) {
-                                        const auto compositionResult =
-                                                layerFE->stealCompositionResult();
-                                        const auto& fences = compositionResult.releaseFences;
-                                        // CompositionEngine may choose to cull layers that
-                                        // aren't visible, so pass a non-fence.
-                                        return fences.empty() ? Fence::NO_FENCE
-                                                              : fences.back().first.get();
-                                    } else {
-                                        return layerFE->stealCompositionResult()
-                                                .releaseFences.back()
-                                                .first.get();
-                                    }
-                                });
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
+                                    [layerFE = std::move(layerFE)](FenceResult) {
+                                        if (FlagManager::getInstance()
+                                                    .screenshot_fence_preservation()) {
+                                            const auto compositionResult =
+                                                    layerFE->stealCompositionResult();
+                                            const auto& fences = compositionResult.releaseFences;
+                                            // CompositionEngine may choose to cull layers that
+                                            // aren't visible, so pass a non-fence.
+                                            return fences.empty() ? Fence::NO_FENCE
+                                                                  : fences.back().first.get();
+                                        } else {
+                                            return layerFE->stealCompositionResult()
+                                                    .releaseFences.back()
+                                                    .first.get();
+                                        }
+                                    });
+        }
     }
 
     return presentFuture;
@@ -8588,8 +8639,13 @@
             return INVALID_OPERATION;
         } else {
             using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy;
+            const auto idleScreenConfigOpt =
+                    FlagManager::getInstance().idle_screen_refresh_rate_timeout()
+                    ? specs.idleScreenRefreshRateConfig
+                    : std::nullopt;
             const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges),
-                                translate(specs.appRequestRanges), specs.allowGroupSwitching};
+                                translate(specs.appRequestRanges), specs.allowGroupSwitching,
+                                idleScreenConfigOpt};
 
             return setDesiredDisplayModeSpecsInternal(display, policy);
         }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0cc8fbb..c106abd 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -509,10 +509,7 @@
         return lockedDumper(std::bind(dump, this, _1, _2, _3));
     }
 
-    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
-    Dumper mainThreadDumper(F dump) {
-        using namespace std::placeholders;
-        Dumper dumper = std::bind(dump, this, _3);
+    Dumper mainThreadDumperImpl(Dumper dumper) {
         return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
             mScheduler
                     ->schedule(
@@ -522,6 +519,18 @@
         };
     }
 
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper mainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _3));
+    }
+
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper argsMainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _1, _3));
+    }
+
     // Maximum allowed number of display frames that can be set through backdoor
     static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048;
 
@@ -872,6 +881,11 @@
     // Traverse through all the layers and compute and cache its bounds.
     void computeLayerBounds();
 
+    // Creates a promise for a future release fence for a layer. This allows for
+    // the layer to keep track of when its buffer can be released.
+    void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
+
+    // Checks if a protected layer exists in a list of layers.
     bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
 
     void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
@@ -883,12 +897,22 @@
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
 
+    // Overloaded version of renderScreenImpl that is used when layer snapshots have
+    // not yet been captured, and thus cannot yet be passed in as a parameter.
+    // Needed for TestableSurfaceFlinger.
     ftl::SharedFuture<FenceResult> renderScreenImpl(
             std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
             REQUIRES(kMainThreadContext);
 
+    ftl::SharedFuture<FenceResult> renderScreenImpl(
+            std::shared_ptr<const RenderArea>,
+            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+            bool grayscale, bool isProtected, ScreenCaptureResults&,
+            std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) EXCLUDES(mStateLock)
+            REQUIRES(kMainThreadContext);
+
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
     void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid,
@@ -1113,9 +1137,9 @@
     void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
-    void listLayersLocked(std::string& result) const;
-    void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
-    void clearStatsLocked(const DumpArgs& args, std::string& result);
+    void listLayers(std::string& result) const;
+    void dumpStats(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
+    void clearStats(const DumpArgs& args, std::string& result);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 6a66fff..7a0fb5e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -24,6 +24,7 @@
 
 #include <mutex>
 #include <optional>
+#include <set>
 #include <thread>
 
 #include "FrontEnd/DisplayInfo.h"
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 7b5298c..222ae30 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -30,6 +30,7 @@
 #include <cinttypes>
 
 #include <binder/IInterface.h>
+#include <common/FlagManager.h>
 #include <utils/RefBase.h>
 
 namespace android {
@@ -128,9 +129,17 @@
     sp<IBinder> surfaceControl = handle->surfaceControl.promote();
     if (surfaceControl) {
         sp<Fence> prevFence = nullptr;
-        for (const auto& future : handle->previousReleaseFences) {
-            mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& future : handle->previousReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+            }
+        } else {
+            for (const auto& future : handle->previousSharedReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+            }
         }
+
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 245398f..cb7150b 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -46,7 +46,8 @@
     bool releasePreviousBuffer = false;
     std::string name;
     sp<Fence> previousReleaseFence;
-    std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
+    std::vector<ftl::Future<FenceResult>> previousReleaseFences;
+    std::vector<ftl::SharedFuture<FenceResult>> previousSharedReleaseFences;
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     nsecs_t latchTime = -1;
     std::optional<uint32_t> transformHint = std::nullopt;
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 4a89dd0..6b971a7 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -36,6 +36,7 @@
     static_libs: [
         "libsurfaceflingerflags",
         "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
 
@@ -47,6 +48,7 @@
     static_libs: [
         "libsurfaceflingerflags_test",
         "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
 
@@ -59,6 +61,7 @@
         "libsurfaceflinger_common",
         "libsurfaceflingerflags",
         "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
 
@@ -71,5 +74,6 @@
         "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
         "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
     ],
 }
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 3b669c6..6507aba 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -28,6 +28,7 @@
 
 #include <android_os.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
+#include <com_android_server_display_feature_flags.h>
 
 namespace android {
 using namespace com::android::graphics::surfaceflinger;
@@ -110,11 +111,13 @@
 
     /// Trunk stable server flags ///
     DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+    DUMP_SERVER_FLAG(adpf_gpu_sf);
     DUMP_SERVER_FLAG(adpf_use_fmq_channel);
 
     /// Trunk stable readonly flags ///
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+    DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
     DUMP_READ_ONLY_FLAG(misc1);
     DUMP_READ_ONLY_FLAG(vrr_config);
     DUMP_READ_ONLY_FLAG(hotplug2);
@@ -134,6 +137,8 @@
     DUMP_READ_ONLY_FLAG(restore_blur_step);
     DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
     DUMP_READ_ONLY_FLAG(protected_if_client);
+    DUMP_READ_ONLY_FLAG(ce_fence_promise);
+    DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -187,6 +192,9 @@
 #define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \
     FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner)
 
+#define FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, owner)
+
 /// Legacy server flags ///
 FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
 FLAG_MANAGER_LEGACY_SERVER_FLAG(use_adpf_cpu_hint, "debug.sf.enable_adpf_cpu_hint",
@@ -197,6 +205,7 @@
 /// Trunk stable readonly flags ///
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
 FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
 FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
@@ -210,18 +219,24 @@
 FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
 FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
 FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation")
 FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
 FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "")
 FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
 FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
 FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
+FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "")
 
 /// Trunk stable server flags from outside SurfaceFlinger ///
 FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
 
+/// Trunk stable readonly flags from outside SurfaceFlinger ///
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
+                                     com::android::server::display::feature::flags)
+
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 763963e..964943c 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -49,10 +49,12 @@
 
     /// Trunk stable server flags ///
     bool refresh_rate_overlay_on_external_display() const;
+    bool adpf_gpu_sf() const;
     bool adpf_use_fmq_channel() const;
 
     /// Trunk stable readonly flags ///
     bool connected_display() const;
+    bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
     bool misc1() const;
     bool vrr_config() const;
@@ -73,6 +75,8 @@
     bool restore_blur_step() const;
     bool dont_skip_on_early_ro() const;
     bool protected_if_client() const;
+    bool ce_fence_promise() const;
+    bool idle_screen_refresh_rate_timeout() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/common/include/common/test/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
index d61fcb5..5317cbb 100644
--- a/services/surfaceflinger/common/include/common/test/FlagUtils.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h
@@ -18,9 +18,13 @@
 
 #include <common/FlagManager.h>
 
-#define SET_FLAG_FOR_TEST(name, value) \
-    TestFlagSetter _testflag_ {        \
-        (name), (name), (value)        \
+// indirection to resolve __LINE__ in SET_FLAG_FOR_TEST, it's used to create a unique TestFlagSetter
+// setter var name everytime so multiple flags can be set in a test
+#define CONCAT_INNER(a, b) a##b
+#define CONCAT(a, b) CONCAT_INNER(a, b)
+#define SET_FLAG_FOR_TEST(name, value)            \
+    TestFlagSetter CONCAT(_testFlag_, __LINE__) { \
+        (name), (name), (value)                   \
     }
 
 namespace android {
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 5451752..0a5cde3 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,10 +4,39 @@
 container: "system"
 
 flag {
+    name: "adpf_gpu_sf"
+    namespace: "game"
+    description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
+    bug: "284324521"
+} # adpf_gpu_sf
+
+flag {
+  name: "ce_fence_promise"
+  namespace: "window_surfaces"
+  description: "Moves logic for buffer release fences into LayerFE"
+  bug: "294936197"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "dont_skip_on_early_ro2"
   namespace: "core_graphics"
   description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
   bug: "273702768"
 } # dont_skip_on_early_ro2
 
+flag {
+  name: "frame_rate_category_mrr"
+  namespace: "core_graphics"
+  description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
+  bug: "330224639"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # frame_rate_category_mrr
+
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 925fe0b..38fc977 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -38,10 +38,10 @@
         "DereferenceSurfaceControl_test.cpp",
         "DisplayConfigs_test.cpp",
         "DisplayEventReceiver_test.cpp",
+        "Dumpsys_test.cpp",
         "EffectLayer_test.cpp",
         "HdrSdrRatioOverlay_test.cpp",
         "InvalidHandles_test.cpp",
-        "LayerBorder_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
         "LayerState_test.cpp",
@@ -67,6 +67,7 @@
     static_libs: [
         "liblayers_proto",
         "android.hardware.graphics.composer@2.1",
+        "libsurfaceflinger_common",
     ],
     shared_libs: [
         "android.hardware.graphics.common@1.2",
@@ -82,6 +83,7 @@
         "libprotobuf-cpp-full",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
     header_libs: [
         "libnativewindow_headers",
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 822ac4d..9b83713 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -241,7 +241,7 @@
     // Check with root.
     {
         UIDFaker f(AID_ROOT);
-        ASSERT_FALSE(condition());
+        ASSERT_TRUE(condition());
     }
 
     // Check as a Graphics user.
diff --git a/services/surfaceflinger/tests/Dumpsys_test.cpp b/services/surfaceflinger/tests/Dumpsys_test.cpp
new file mode 100644
index 0000000..c3914e5
--- /dev/null
+++ b/services/surfaceflinger/tests/Dumpsys_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 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/native_window.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include "android-base/stringprintf.h"
+#include "utils/Errors.h"
+
+namespace android {
+
+namespace {
+status_t runShellCommand(const std::string& cmd, std::string& result) {
+    FILE* pipe = popen(cmd.c_str(), "r");
+    if (!pipe) {
+        return UNKNOWN_ERROR;
+    }
+
+    char buffer[1024];
+    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
+        result += buffer;
+    }
+
+    pclose(pipe);
+    return OK;
+}
+} // namespace
+
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+class WaitForCompletedCallback {
+public:
+    WaitForCompletedCallback() = default;
+    ~WaitForCompletedCallback() = default;
+
+    static void transactionCompletedCallback(void* callbackContext, nsecs_t /* latchTime */,
+                                             const sp<Fence>& /* presentFence */,
+                                             const std::vector<SurfaceControlStats>& /* stats */) {
+        ASSERT_NE(callbackContext, nullptr) << "failed to get callback context";
+        WaitForCompletedCallback* context = static_cast<WaitForCompletedCallback*>(callbackContext);
+        context->notify();
+    }
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST(Dumpsys, listLayers) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    std::string layersAsString;
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --list", layersAsString));
+    EXPECT_NE(strstr(layersAsString.c_str(), ""), nullptr);
+}
+
+TEST(Dumpsys, stats) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    uint64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+            BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+
+    sp<GraphicBuffer> buffer =
+            sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, 1u, usageFlags, "test");
+
+    WaitForCompletedCallback callback;
+    SurfaceComposerClient::Transaction()
+            .setBuffer(newLayer, buffer)
+            .addTransactionCompletedCallback(WaitForCompletedCallback::transactionCompletedCallback,
+                                             &callback)
+            .apply();
+    callback.wait();
+    std::string stats;
+    std::string layerName = base::StringPrintf("MY_TEST_LAYER#%d", newLayer->getLayerId());
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency " + layerName, stats));
+    EXPECT_NE(std::string(""), stats);
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency-clear " + layerName, stats));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp
deleted file mode 100644
index 00e134b..0000000
--- a/services/surfaceflinger/tests/LayerBorder_test.cpp
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-// TODO: Amend all tests when screenshots become fully reworked for borders
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <chrono> // std::chrono::seconds
-#include <thread> // std::this_thread::sleep_for
-#include "LayerTransactionTest.h"
-
-namespace android {
-
-class LayerBorderTest : public LayerTransactionTest {
-protected:
-    virtual void SetUp() {
-        LayerTransactionTest::SetUp();
-        ASSERT_EQ(NO_ERROR, mClient->initCheck());
-
-        toHalf3 = ColorTransformHelper::toHalf3;
-        toHalf4 = ColorTransformHelper::toHalf4;
-
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        ASSERT_FALSE(ids.empty());
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        ASSERT_FALSE(display == nullptr);
-        mColorOrange = toHalf4({255, 140, 0, 255});
-        mParentLayer = createColorLayer("Parent layer", Color::RED);
-
-        mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */,
-                                                 0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                                 ISurfaceComposerClient::eFXSurfaceContainer |
-                                                         ISurfaceComposerClient::eNoColorFill,
-                                                 mParentLayer->getHandle());
-        EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer";
-
-        mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-        EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1";
-
-        mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-
-        EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2";
-
-        asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-            t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer);
-            t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
-
-            t.setColor(mEffectLayer1, toHalf3(Color::BLUE));
-
-            t.setColor(mEffectLayer2, toHalf3(Color::GREEN));
-        });
-    }
-
-    virtual void TearDown() {
-        // Uncomment the line right below when running any of the tests
-        // std::this_thread::sleep_for (std::chrono::seconds(30));
-        LayerTransactionTest::TearDown();
-        mParentLayer = 0;
-    }
-
-    std::function<half3(Color)> toHalf3;
-    std::function<half4(Color)> toHalf4;
-    sp<SurfaceControl> mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2;
-    half4 mColorOrange;
-};
-
-TEST_F(LayerBorderTest, OverlappingVisibleRegions) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, EmptyVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, ZOrderAdjustment) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setLayer(mParentLayer, 10);
-        t.setLayer(mEffectLayer1, 30);
-        t.setLayer(mEffectLayer2, 20);
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, GrandChildHierarchy) {
-    sp<SurfaceControl> containerLayer2 =
-            mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceContainer |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   mContainerLayer->getHandle());
-    EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2";
-
-    sp<SurfaceControl> effectLayer3 =
-            mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceEffect |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   containerLayer2->getHandle());
-
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setCrop(effectLayer3, Rect(400, 400, 800, 800));
-        t.setColor(effectLayer3, toHalf3(Color::BLUE));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(effectLayer3);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, TransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer1, 0.0f);
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, SemiTransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer2, 0.5f);
-
-        t.enableBorder(mEffectLayer2, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, InvisibleLayers) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, LayerWithBuffer) {
-    asTransaction([&](Transaction& t) {
-        t.hide(mEffectLayer1);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-
-        sp<SurfaceControl> layer =
-                mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
-                                       PIXEL_FORMAT_RGBA_8888,
-                                       ISurfaceComposerClient::eFXSurfaceBufferState,
-                                       mContainerLayer->getHandle());
-
-        sp<GraphicBuffer> buffer =
-                sp<GraphicBuffer>::make(400u, 400u, PIXEL_FORMAT_RGBA_8888, 1u,
-                                        BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
-                                                BufferUsage::COMPOSER_OVERLAY |
-                                                BufferUsage::GPU_TEXTURE,
-                                        "test");
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN);
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE);
-
-        t.setBuffer(layer, buffer);
-        t.setPosition(layer, 100, 100);
-        t.show(layer);
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidth) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 50, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomColor) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 79886bd..b4496d3 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -1295,4 +1295,74 @@
     }
 }
 
+TEST_F(LayerCallbackTest, OccludedLayerHasReleaseCallback) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1a, callback1b, callback2a, callback2b;
+    int err = fillTransaction(transaction1, &callback1a, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2a, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    ui::Size bufferSize = getBufferSize();
+
+    // Occlude layer1 with layer2
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    ExpectedResult expected1a, expected1b, expected2a, expected2b;
+    expected1a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    expected2a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1a, expected1a, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2a, expected2a, true));
+
+    // Submit new buffers so previous buffers can be released
+    err = fillTransaction(transaction1, &callback1b, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2b, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    expected1b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    expected2b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1b, expected1b, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2b, expected2b, true));
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
index 15ff696..7fce7e9 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
@@ -18,9 +18,12 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "gui/SurfaceComposerClient.h"
+#include "ui/DisplayId.h"
 
 namespace android {
 
@@ -37,7 +40,8 @@
 
         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         ASSERT_FALSE(ids.empty());
-        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        mMainDisplayId = ids.front();
+        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(mMainDisplayId);
         SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState);
         SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode);
 
@@ -85,6 +89,7 @@
     ui::DisplayState mMainDisplayState;
     ui::DisplayMode mMainDisplayMode;
     sp<IBinder> mMainDisplay;
+    PhysicalDisplayId mMainDisplayId;
     sp<IBinder> mVirtualDisplay;
     sp<IGraphicBufferProducer> mProducer;
     sp<SurfaceControl> mColorLayer;
@@ -119,6 +124,9 @@
     createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK);
     createColorLayer(ui::DEFAULT_LAYER_STACK);
 
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
     asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
 
     // Verify color layer renders correctly on main display and it is mirrored on the
@@ -133,6 +141,37 @@
     sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
 }
 
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
+    // Create a display and use a unique layerstack ID for mirrorDisplay() so
+    // the contents of the main display are mirrored on to the virtual display.
+
+    // A unique layerstack ID must be used because sharing the same layerFE
+    // with more than one display is unsupported. A unique layerstack ensures
+    // that a different layerFE is used between displays.
+    constexpr ui::LayerStack layerStack{77687666}; // ASCII for MDLB (MultiDisplayLayerBounds)
+    createDisplay(mMainDisplayState.layerStackSpaceRect, layerStack);
+    createColorLayer(ui::DEFAULT_LAYER_STACK);
+
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
+    asTransaction([&](Transaction& t) {
+        t.setPosition(mColorLayer, 10, 10);
+        t.setLayerStack(mirrorSc, layerStack);
+    });
+
+    // Verify color layer renders correctly on main display and it is mirrored on the
+    // virtual display.
+    std::unique_ptr<ScreenCapture> sc;
+    ScreenCapture::captureScreen(&sc, mMainDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+
+    ScreenCapture::captureScreen(&sc, mVirtualDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 797a64c..87e6d3e 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,9 +16,11 @@
 #ifndef ANDROID_TRANSACTION_TEST_HARNESSES
 #define ANDROID_TRANSACTION_TEST_HARNESSES
 
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -36,9 +38,10 @@
             case RenderPath::VIRTUAL_DISPLAY:
 
                 const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+                const PhysicalDisplayId displayId = ids.front();
                 const auto displayToken = ids.empty()
                         ? nullptr
-                        : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+                        : SurfaceComposerClient::getPhysicalDisplayToken(displayId);
 
                 ui::DisplayState displayState;
                 SurfaceComposerClient::getDisplayState(displayToken, &displayState);
@@ -66,11 +69,21 @@
                 vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"),
                                                                 false /*secure*/);
 
+                constexpr ui::LayerStack layerStack{
+                        848472}; // ASCII for TTH (TransactionTestHarnesses)
+                sp<SurfaceControl> mirrorSc =
+                        SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
+
                 SurfaceComposerClient::Transaction t;
                 t.setDisplaySurface(vDisplay, producer);
-                t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
                 t.setDisplayProjection(vDisplay, displayState.orientation,
                                        Rect(displayState.layerStackSpaceRect), Rect(resolution));
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setDisplayLayerStack(vDisplay, layerStack);
+                    t.setLayerStack(mirrorSc, layerStack);
+                } else {
+                    t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
+                }
                 t.apply();
                 SurfaceComposerClient::Transaction().apply(true);
 
@@ -85,6 +98,15 @@
                 constexpr bool kContainsHdr = false;
                 auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer, kContainsHdr);
                 itemConsumer->releaseBuffer(item);
+
+                // Possible race condition with destroying virtual displays, in which
+                // CompositionEngine::present may attempt to be called on the same
+                // display multiple times. The layerStack is set to invalid here so
+                // that the display is ignored if that scenario occurs.
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK);
+                    t.apply(true);
+                }
                 SurfaceComposerClient::destroyDisplay(vDisplay);
                 return sc;
         }
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0adf0b6..51b5f40 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -85,7 +85,7 @@
     EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) {
     mFlagManager.markBootIncomplete();
     EXPECT_DEATH(FlagManager::getInstance()
         .refresh_rate_overlay_on_external_display(), "");
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 110f324..2fb80b1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -274,6 +274,8 @@
 }
 
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     createLegacyAndFrontedEndLayer(1);
     setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 9b8ff42..c63aaeb 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -548,7 +548,41 @@
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
+                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+    // Set default to Min so it is obvious that the vote reset triggered.
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr.
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -588,6 +622,8 @@
 // This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
 // the category is NoPreference.
 TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -617,6 +653,8 @@
 }
 
 TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 3baa48d..94989aa 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,6 +17,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <common/test/FlagUtils.h>
 #include <renderengine/mock/FakeExternalTexture.h>
 
 #include "FrontEnd/LayerHierarchy.h"
@@ -26,6 +27,8 @@
 #include "LayerHierarchyTest.h"
 #include "ui/GraphicTypes.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
     ({                                                                     \
         SCOPED_TRACE("");                                                  \
@@ -42,6 +45,7 @@
 
 using ftl::Flags;
 using namespace ftl::flag_operators;
+using namespace com::android::graphics::surfaceflinger;
 
 // To run test:
 /**
@@ -668,6 +672,8 @@
 // This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
 // interaction also works correctly with the setFrameRate API within SF frontend.
 TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11 (frame rate set to 244.f)
@@ -864,6 +870,8 @@
 }
 
 TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index d9343c7..86ad86e 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -20,6 +20,8 @@
 #include <DisplayHardware/PowerAdvisor.h>
 #include <android_os.h>
 #include <binder/Status.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/FlagManager.h>
 #include <common/test/FlagUtils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -51,6 +53,20 @@
     void setTimingTestingMode(bool testinMode);
     void allowReportActualToAcquireMutex();
     bool sessionExists();
+    int64_t toNanos(Duration d);
+
+    struct GpuTestConfig {
+        bool adpfGpuFlagOn;
+        Duration frame1GpuFenceDuration;
+        Duration frame2GpuFenceDuration;
+        Duration vsyncPeriod;
+        Duration presentDuration = 0ms;
+        Duration postCompDuration = 0ms;
+        bool frame1RequiresRenderEngine;
+        bool frame2RequiresRenderEngine;
+    };
+
+    WorkDuration testGpuScenario(GpuTestConfig& config);
 
 protected:
     TestableSurfaceFlinger mFlinger;
@@ -65,6 +81,10 @@
     return mPowerAdvisor->mHintSession != nullptr;
 }
 
+int64_t PowerAdvisorTest::toNanos(Duration d) {
+    return std::chrono::nanoseconds(d).count();
+}
+
 void PowerAdvisorTest::SetUp() {
     mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
     mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
@@ -117,6 +137,71 @@
     mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
 }
 
+WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf,
+                      config.adpfGpuFlagOn);
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0),
+                                      GpuVirtualDisplayId(1)};
+    mPowerAdvisor->setDisplays(displayIds);
+    auto display1 = displayIds[0];
+    // 60hz
+
+    TimePoint startTime = TimePoint::now();
+    // advisor only starts on frame 2 so do an initial frame
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame1RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame1GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame1GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+
+    // increment the frame
+    std::this_thread::sleep_for(config.vsyncPeriod);
+    startTime = TimePoint::now();
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame2RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame2GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame2GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + config.presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + config.presentDuration + config.postCompDuration);
+
+    // don't report timing for the HWC
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime, startTime);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime, startTime);
+
+    std::vector<aidl::android::hardware::power::WorkDuration> durationReq;
+    EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_))
+            .Times(1)
+            .WillOnce(DoAll(testing::SaveArg<0>(&durationReq),
+                            testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    mPowerAdvisor->reportActualWorkDuration();
+    EXPECT_EQ(durationReq.size(), 1u);
+    return durationReq[0];
+}
+
 Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
     return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
                          : PowerAdvisor::kFenceWaitStartDelayValidated);
@@ -390,5 +475,148 @@
     mPowerAdvisor->startPowerHintSession({1, 2, 3});
 }
 
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked buffer fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(30ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // added a margin as a workaround since we set GPU start time at the time of fence set
+            // call
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = 51ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(50ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(51ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFenceFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = 50ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(50ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(50ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res = testGpuScenario(config);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
 } // namespace
 } // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a155f5d..cf9a7d3 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -103,8 +103,9 @@
     auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
 
     auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                             GlobalSignals signals = {}) const {
-        const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+                             GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
+        const auto result =
+                RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps);
 
         EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
                                    ScoredFrameRate::DescendingScore{}));
@@ -114,8 +115,8 @@
 
     auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
                                      GlobalSignals signals) const {
-        const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
-        return std::make_pair(ranking, consideredSignals);
+        const auto result = getRankedFrameRates(layers, signals);
+        return std::make_pair(result.ranking, result.consideredSignals);
     }
 
     FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
@@ -1387,7 +1388,7 @@
 TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+    auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
 
     auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
@@ -1471,10 +1472,32 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, pacesetterConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+    constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].vote = LayerVoteType::Min;
+
+    // The pacesetterFps takes precedence over the LayerRequirement.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz);
+        EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+
+    // The pacesetterFps takes precedence over GlobalSignals.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz);
+        EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest, touchConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [_, signals] = selector.getRankedFrameRates({}, {});
+    auto signals = selector.getRankedFrameRates({}, {}).consideredSignals;
     EXPECT_FALSE(signals.touch);
 
     std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -2055,7 +2078,7 @@
     constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms;
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120,
                                    Config{
-                                           .idleTimerTimeout = kIdleTimerTimeoutMs,
+                                           .legacyIdleTimerTimeout = kIdleTimerTimeoutMs,
                                    });
     ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
     ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
@@ -2363,7 +2386,7 @@
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
 
-    const auto [rankedFrameRate, signals] =
+    const auto [rankedFrameRate, signals, _] =
             selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
 
     EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
@@ -2587,7 +2610,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
 
-    const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+    const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
@@ -2971,7 +2994,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
@@ -3121,16 +3144,17 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     using GlobalSignals = RefreshRateSelector::GlobalSignals;
-    const auto args = std::make_pair(std::vector<LayerRequirement>{},
-                                     GlobalSignals{.touch = true, .idle = true});
-
     const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
                                                                   {90_Hz, kMode90}}},
                                                           GlobalSignals{.touch = true}};
 
-    selector.mutableGetRankedRefreshRatesCache() = {args, result};
+    selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector<LayerRequirement>{},
+                                                    .signals = GlobalSignals{.touch = true,
+                                                                             .idle = true},
+                                                    .result = result};
 
-    EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+    const auto& cache = *selector.mutableGetRankedRefreshRatesCache();
+    EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals));
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
@@ -3138,15 +3162,18 @@
 
     EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
 
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const Fps pacesetterFps = 60_Hz;
 
-    const auto result = selector.getRankedFrameRates(layers, globalSignals);
+    const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps);
 
     const auto& cache = selector.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
 
-    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->layers, layers);
+    EXPECT_EQ(cache->signals, globalSignals);
+    EXPECT_EQ(cache->pacesetterFps, pacesetterFps);
     EXPECT_EQ(cache->result, result);
 }
 
@@ -4073,7 +4100,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index d4735c7..7479a4f 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -362,7 +362,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{60_Hz,
                                                                                kDisplay2Mode60},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
                                                                      {.weight = 1.f}};
@@ -381,7 +381,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
@@ -400,7 +400,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -422,10 +422,10 @@
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
                                    globalSignals)(kDisplayId2,
                                                   FrameRateMode{120_Hz, kDisplay2Mode120},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                                  GlobalSignals{})(kDisplayId3,
+                                                                   FrameRateMode{60_Hz,
+                                                                                 kDisplay3Mode60},
+                                                                   GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -440,12 +440,12 @@
         expectedChoices = ftl::init::map<
                 const PhysicalDisplayId&,
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
-                                   globalSignals)(kDisplayId2,
-                                                  FrameRateMode{60_Hz, kDisplay2Mode60},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                   GlobalSignals{})(kDisplayId2,
+                                                    FrameRateMode{60_Hz, kDisplay2Mode60},
+                                                    GlobalSignals{})(kDisplayId3,
+                                                                     FrameRateMode{60_Hz,
+                                                                                   kDisplay3Mode60},
+                                                                     globalSignals);
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -607,10 +607,10 @@
 
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(4500)));
+                                             TimePoint::fromNs(5500)));
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(6500)));
+                                             TimePoint::fromNs(7500)));
 }
 
 TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 48707cb..eafba0a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -704,14 +704,17 @@
     EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
 
     vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
-    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
-    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
-    EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
-    EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
-    EXPECT_EQ(11000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
-    EXPECT_EQ(13000, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
-    EXPECT_EQ(17000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
-    EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(11500, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(13500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(16500, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+    EXPECT_EQ(20500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+    // matches the previous cadence
+    EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(20500, 20500));
 }
 
 TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) {
@@ -820,7 +823,7 @@
 
     vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
     vrrTracker.addVsyncTimestamp(0);
-    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
+    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
 }
 
 TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
@@ -944,6 +947,27 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000));
 }
 
+// b/329310308
+TEST_F(VSyncPredictorTest, renderRateChangeAfterAppliedImmediately) {
+    tracker.addVsyncTimestamp(1000);
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(2000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(2001), Eq(3000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(4000), /*applyImmediately*/ false);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(13000));
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 8d9623d..e3aa4ef 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -195,9 +195,7 @@
 
 TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
-
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
     nsecs_t const newPeriod = 5000;
 
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -207,7 +205,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, std::nullopt, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
@@ -463,8 +461,7 @@
 
 TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
 
     nsecs_t const newPeriod = 5000;
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -476,7 +473,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, newPeriod, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
@@ -486,8 +483,7 @@
                          *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */);
 
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4);
-    idleReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(5);
 
     // First, set the same period, which should only be confirmed when we receive two
     // matching callbacks
@@ -512,7 +508,7 @@
     EXPECT_FALSE(idleReactor.addHwVsyncTimestamp(20000, 5000, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index 8e8eb1d..e8630ba 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -40,6 +40,7 @@
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -49,8 +50,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 38c422a..07916b6 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,10 +33,6 @@
     static const Color WHITE;
     static const Color BLACK;
     static const Color TRANSPARENT;
-
-    half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
-
-    half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
 };
 
 const Color Color::RED{255, 0, 0, 255};
@@ -85,14 +81,6 @@
         }
         color = ret;
     }
-
-    static half3 toHalf3(const Color& color) {
-        return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
-    }
-
-    static half4 toHalf4(const Color& color) {
-        return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
-    }
 };
 } // namespace
 } // namespace android
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 6ba2eb1..74d3d9d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -526,15 +526,12 @@
     *count = num_copied;
 }
 
-PixelFormat GetNativePixelFormat(VkFormat format,
-                                 VkCompositeAlphaFlagBitsKHR alpha) {
+PixelFormat GetNativePixelFormat(VkFormat format) {
     PixelFormat native_format = PixelFormat::RGBA_8888;
     switch (format) {
         case VK_FORMAT_R8G8B8A8_UNORM:
         case VK_FORMAT_R8G8B8A8_SRGB:
-            native_format = alpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
-                                ? PixelFormat::RGBX_8888
-                                : PixelFormat::RGBA_8888;
+            native_format = PixelFormat::RGBA_8888;
             break;
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
             native_format = PixelFormat::RGB_565;
@@ -904,7 +901,7 @@
     VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
     ATRACE_CALL();
 
-    auto surface_handle = pSurfaceInfo->surface;
+    auto surface = pSurfaceInfo->surface;
     auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
 
     VkSurfacePresentModeEXT const *pPresentMode = nullptr;
@@ -925,13 +922,7 @@
     int transform_hint;
     int max_buffer_count;
     int min_undequeued_buffers;
-    // On Android, window composition is a WindowManager property, not something
-    // associated with the bufferqueue. It can't be changed from here for a
-    // swapchain connected with SurfaceFlinger. For offscreen surfaces, it's
-    // allowed to report opaque being supported for RGBX preference.
-    VkCompositeAlphaFlagsKHR composite_alpha =
-        VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
-    if (surface_handle == VK_NULL_HANDLE) {
+    if (surface == VK_NULL_HANDLE) {
         const InstanceData& instance_data = GetData(physicalDevice);
         ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
         bool surfaceless_enabled =
@@ -952,8 +943,7 @@
         capabilities->minImageCount = 0xFFFFFFFF;
         capabilities->maxImageCount = 0xFFFFFFFF;
     } else {
-        Surface& surface = *SurfaceFromHandle(surface_handle);
-        ANativeWindow* window = surface.window.get();
+        ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
 
         err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
         if (err != android::OK) {
@@ -1028,11 +1018,6 @@
                     min_undequeued_buffers + default_additional_buffers);
             capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
         }
-
-        if (!(surface.consumer_usage &
-              AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY)) {
-            composite_alpha |= VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
-        }
     }
 
     capabilities->currentExtent =
@@ -1059,7 +1044,9 @@
     capabilities->currentTransform =
         TranslateNativeToVulkanTransform(transform_hint);
 
-    capabilities->supportedCompositeAlpha = composite_alpha;
+    // On Android, window composition is a WindowManager property, not something
+    // associated with the bufferqueue. It can't be changed from here.
+    capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
 
     capabilities->supportedUsageFlags =
         VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
@@ -1658,21 +1645,20 @@
 
     ALOGV("vkCreateSwapchainKHR: surface=0x%" PRIx64
           " minImageCount=%u imageFormat=%u imageColorSpace=%u"
-          " imageExtent=%ux%u imageUsage=%#x preTransform=%u compositeAlpha=%u"
-          " presentMode=%u oldSwapchain=0x%" PRIx64,
+          " imageExtent=%ux%u imageUsage=%#x preTransform=%u presentMode=%u"
+          " oldSwapchain=0x%" PRIx64,
           reinterpret_cast<uint64_t>(create_info->surface),
           create_info->minImageCount, create_info->imageFormat,
           create_info->imageColorSpace, create_info->imageExtent.width,
           create_info->imageExtent.height, create_info->imageUsage,
-          create_info->preTransform, create_info->compositeAlpha,
-          create_info->presentMode,
+          create_info->preTransform, create_info->presentMode,
           reinterpret_cast<uint64_t>(create_info->oldSwapchain));
 
     if (!allocator)
         allocator = &GetData(device).allocator;
 
-    PixelFormat native_pixel_format = GetNativePixelFormat(
-        create_info->imageFormat, create_info->compositeAlpha);
+    PixelFormat native_pixel_format =
+        GetNativePixelFormat(create_info->imageFormat);
     DataSpace native_dataspace = GetNativeDataspace(
         create_info->imageColorSpace, create_info->imageFormat);
     if (native_dataspace == DataSpace::UNKNOWN) {