Merge "Add the transform type to the Winscope protobuf"
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index f9c8c8a..bdbc0d3 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -31,6 +31,7 @@
 using ::android::sp;
 using ::android::status_t;
 using ::android::String16;
+using ::android::String8;
 using ::android::wp;
 
 namespace ABBinderTag {
@@ -67,8 +68,6 @@
 AIBinder::~AIBinder() {}
 
 bool AIBinder::associateClass(const AIBinder_Class* clazz) {
-    using ::android::String8;
-
     if (clazz == nullptr) return false;
     if (mClazz == clazz) return true;
 
@@ -119,6 +118,33 @@
     return getClass()->getInterfaceDescriptor();
 }
 
+status_t ABBinder::dump(int fd, const ::android::Vector<String16>& args) {
+    AIBinder_onDump onDump = getClass()->onDump;
+
+    if (onDump == nullptr) {
+        return STATUS_OK;
+    }
+
+    // technically UINT32_MAX would be okay here, but INT32_MAX is expected since this may be
+    // null in Java
+    if (args.size() > INT32_MAX) {
+        LOG(ERROR) << "ABBinder::dump received too many arguments: " << args.size();
+        return STATUS_BAD_VALUE;
+    }
+
+    std::vector<String8> utf8Args;  // owns memory of utf8s
+    utf8Args.reserve(args.size());
+    std::vector<const char*> utf8Pointers;  // what can be passed over NDK API
+    utf8Pointers.reserve(args.size());
+
+    for (size_t i = 0; i < args.size(); i++) {
+        utf8Args.push_back(String8(args[i]));
+        utf8Pointers.push_back(utf8Args[i].c_str());
+    }
+
+    return onDump(this, fd, utf8Pointers.data(), utf8Pointers.size());
+}
+
 status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parcel* reply,
                               binder_flags_t flags) {
     if (isUserCommand(code)) {
@@ -232,6 +258,13 @@
     return new AIBinder_Class(interfaceDescriptor, onCreate, onDestroy, onTransact);
 }
 
+void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) {
+    CHECK(clazz != nullptr) << "setOnDump requires non-null clazz";
+
+    // this is required to be called before instances are instantiated
+    clazz->onDump = onDump;
+}
+
 void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
     CHECK(who == mWho);
 
@@ -325,6 +358,30 @@
     return PruneStatusT(binder->getBinder()->pingBinder());
 }
 
+binder_status_t AIBinder_dump(AIBinder* binder, int fd, const char** args, uint32_t numArgs) {
+    if (binder == nullptr) {
+        return STATUS_UNEXPECTED_NULL;
+    }
+
+    ABBinder* bBinder = binder->asABBinder();
+    if (bBinder != nullptr) {
+        AIBinder_onDump onDump = binder->getClass()->onDump;
+        if (onDump == nullptr) {
+            return STATUS_OK;
+        }
+        return PruneStatusT(onDump(bBinder, fd, args, numArgs));
+    }
+
+    ::android::Vector<String16> utf16Args;
+    utf16Args.setCapacity(numArgs);
+    for (uint32_t i = 0; i < numArgs; i++) {
+        utf16Args.push(String16(String8(args[i])));
+    }
+
+    status_t status = binder->getBinder()->dump(fd, utf16Args);
+    return PruneStatusT(status);
+}
+
 binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                      void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 7852298..0dd795a 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -25,6 +25,7 @@
 
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
+#include <utils/Vector.h>
 
 inline bool isUserCommand(transaction_code_t code) {
     return code >= FIRST_CALL_TRANSACTION && code <= LAST_CALL_TRANSACTION;
@@ -66,6 +67,7 @@
     ABBinder* asABBinder() override { return this; }
 
     const ::android::String16& getInterfaceDescriptor() const override;
+    ::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;
 
@@ -106,10 +108,14 @@
 
     const ::android::String16& getInterfaceDescriptor() const { return mInterfaceDescriptor; }
 
+    // required to be non-null, implemented for every class
     const AIBinder_Class_onCreate onCreate;
     const AIBinder_Class_onDestroy onDestroy;
     const AIBinder_Class_onTransact onTransact;
 
+    // optional methods for a class
+    AIBinder_onDump onDump;
+
    private:
     // This must be a String16 since BBinder virtual getInterfaceDescriptor returns a reference to
     // one.
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 9c6c55e..bddc10d 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -179,6 +179,31 @@
         __INTRODUCED_IN(29);
 
 /**
+ * Dump information about an AIBinder (usually for debugging).
+ *
+ * When no arguments are provided, a brief overview of the interview should be given.
+ *
+ * \param binder interface being dumped
+ * \param fd file descriptor to be dumped to, should be flushed, ownership is not passed.
+ * \param args array of null-terminated strings for dump (may be null if numArgs is 0)
+ * \param numArgs number of args to be sent
+ *
+ * \return binder_status_t result of transaction (if remote, for instance)
+ */
+typedef binder_status_t (*AIBinder_onDump)(AIBinder* binder, int fd, const char** args,
+                                           uint32_t numArgs);
+
+/**
+ * This sets the implementation of the dump method for a class.
+ *
+ * If this isn't set, nothing will be dumped when dump is called (for instance with
+ * android.os.Binder#dump). Must be called before any instance of the class is created.
+ *
+ * \param dump function to call when an instance of this binder class is being dumped.
+ */
+void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) __INTRODUCED_IN(29);
+
+/**
  * Creates a new binder object of the appropriate class.
  *
  * Ownership of args is passed to this object. The lifecycle is implemented with AIBinder_incStrong
@@ -237,6 +262,21 @@
 binder_status_t AIBinder_ping(AIBinder* binder) __INTRODUCED_IN(29);
 
 /**
+ * Built-in transaction for all binder objects. This dumps information about a given binder.
+ *
+ * See also AIBinder_Class_setOnDump, AIBinder_onDump
+ *
+ * \param binder the binder to dump information about
+ * \param fd where information should be dumped to
+ * \param args null-terminated arguments to pass (may be null if numArgs is 0)
+ * \param numArgs number of args to send
+ *
+ * \return STATUS_OK if dump succeeds (or if there is nothing to dump)
+ */
+binder_status_t AIBinder_dump(AIBinder* binder, int fd, const char** args, uint32_t numArgs)
+        __INTRODUCED_IN(29);
+
+/**
  * Registers for notifications that the associated binder is dead. The same death recipient may be
  * associated with multiple different binders. If the binder is local, then no death recipient will
  * be given (since if the local process dies, then no recipient will exist to recieve a
diff --git a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
index a42c60b..b82141c 100644
--- a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
@@ -104,6 +104,30 @@
      * this will be checked using AIBinder_isRemote.
      */
     virtual bool isRemote() = 0;
+
+    /**
+     * Dumps information about the interface.
+     */
+    virtual binder_status_t dump(int /*fd*/, const char** /*args*/, uint32_t /*numArgs*/) {
+        return STATUS_OK;
+    }
+
+    /**
+     * Helper method to create a class
+     */
+    static AIBinder_Class* defineClass(const char* interfaceDescriptor,
+                                       AIBinder_Class_onCreate onCreate,
+                                       AIBinder_Class_onDestroy onDestroy,
+                                       AIBinder_Class_onTransact onTransact,
+                                       AIBinder_onDump onDump = nullptr) {
+        AIBinder_Class* clazz =
+                AIBinder_Class_define(interfaceDescriptor, onCreate, onDestroy, onTransact);
+        if (clazz == nullptr) {
+            return nullptr;
+        }
+        AIBinder_Class_setOnDump(clazz, onDump);
+        return clazz;
+    }
 };
 
 /**
@@ -144,6 +168,10 @@
 
     bool isRemote() override { return AIBinder_isRemote(mBinder.get()); }
 
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override {
+        return AIBinder_dump(asBinder().get(), fd, args, numArgs);
+    }
+
    private:
     SpAIBinder mBinder;
 };
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 655f4d5..7e65817 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -2,10 +2,12 @@
   global:
     AIBinder_associateClass;
     AIBinder_Class_define;
+    AIBinder_Class_setOnDump;
     AIBinder_DeathRecipient_delete;
     AIBinder_DeathRecipient_new;
     AIBinder_debugGetRefCount;
     AIBinder_decStrong;
+    AIBinder_dump;
     AIBinder_fromJavaBinder;
     AIBinder_getCallingPid;
     AIBinder_getCallingUid;
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 4cddf94..5fd4a95 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -45,14 +45,14 @@
 namespace android {
 namespace bpf {
 
-typedef struct {
+struct time_key_t {
     uint32_t uid;
     uint32_t freq;
-} time_key_t;
+};
 
-typedef struct {
+struct val_t {
     uint64_t ar[100];
-} val_t;
+};
 
 static std::mutex gInitializedMutex;
 static bool gInitialized = false;
@@ -65,19 +65,13 @@
 static bool readNumbersFromFile(const std::string &path, std::vector<uint32_t> *out) {
     std::string data;
 
-    if (!android::base::ReadFileToString(path, &data)) {
-        ALOGD("Failed to read file %s", path.c_str());
-        return false;
-    }
+    if (!android::base::ReadFileToString(path, &data)) return false;
 
     auto strings = android::base::Split(data, " \n");
     for (const auto &s : strings) {
         if (s.empty()) continue;
         uint32_t n;
-        if (!android::base::ParseUint(s, &n)) {
-            ALOGD("Failed to parse file %s", path.c_str());
-            return false;
-        }
+        if (!android::base::ParseUint(s, &n)) return false;
         out->emplace_back(n);
     }
     return true;
@@ -141,16 +135,8 @@
     std::string path = StringPrintf(BPF_FS_PATH "prog_time_in_state_tracepoint_%s_%s",
                                     eventType.c_str(), eventName.c_str());
     int prog_fd = bpf_obj_get(path.c_str());
-    if (prog_fd < 0) {
-        ALOGD("bpf_obj_get() failed for program %s", path.c_str());
-        return false;
-    }
-    if (bpf_attach_tracepoint(prog_fd, eventType.c_str(), eventName.c_str()) < 0) {
-        ALOGD("Failed to attach bpf program to tracepoint %s/%s", eventType.c_str(),
-              eventName.c_str());
-        return false;
-    }
-    return true;
+    if (prog_fd < 0) return false;
+    return bpf_attach_tracepoint(prog_fd, eventType.c_str(), eventName.c_str()) >= 0;
 }
 
 // Start tracking and aggregating data to be reported by getUidCpuFreqTimes and getUidsCpuFreqTimes.
@@ -224,20 +210,16 @@
     auto fn = [freqTimeMap, &policyFreqIdxs](const time_key_t &key, const val_t &val,
                                              const BpfMap<time_key_t, val_t> &) {
         if (freqTimeMap->find(key.uid) == freqTimeMap->end()) {
-            std::vector<std::vector<uint64_t>> v;
+            (*freqTimeMap)[key.uid].resize(gNPolicies);
             for (uint32_t i = 0; i < gNPolicies; ++i) {
-                std::vector<uint64_t> v2(gPolicyFreqs[i].size(), 0);
-                v.emplace_back(v2);
+                (*freqTimeMap)[key.uid][i].resize(gPolicyFreqs[i].size(), 0);
             }
-            (*freqTimeMap)[key.uid] = v;
         }
 
         for (size_t policy = 0; policy < gNPolicies; ++policy) {
             for (const auto &cpu : gPolicyCpus[policy]) {
-                uint32_t cpuTime = val.ar[cpu];
-                if (cpuTime == 0) continue;
                 auto freqIdx = policyFreqIdxs[policy][key.freq];
-                (*freqTimeMap)[key.uid][policy][freqIdx] += cpuTime;
+                (*freqTimeMap)[key.uid][policy][freqIdx] += val.ar[cpu];
             }
         }
         return android::netdutils::status::ok;
diff --git a/libs/cputimeinstate/cputimeinstate.h b/libs/cputimeinstate/cputimeinstate.h
index 0205452..9f6103e 100644
--- a/libs/cputimeinstate/cputimeinstate.h
+++ b/libs/cputimeinstate/cputimeinstate.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#pragma once
+
 #include <unordered_map>
 #include <vector>
 
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 3c67542..515209e 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -90,6 +90,7 @@
 
     MotionEvent* event = new MotionEvent();
     PointerCoords coords;
+    coords.clear();
     constexpr size_t pointerCount = 1;
     PointerProperties properties[pointerCount];
 
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index 3ac1c58..2899bcf 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -75,7 +75,7 @@
      * scRGB:
      *
      * The red, green, and blue components are stored in extended sRGB space,
-     * but are linear, not gamma-encoded.
+     * and gamma-encoded using the SRGB transfer function.
      * The RGB primaries and the white point are the same as BT.709.
      *
      * The values are floating point.
diff --git a/services/inputflinger/tests/InputClassifier_test.cpp b/services/inputflinger/tests/InputClassifier_test.cpp
index 20699c9..1651057 100644
--- a/services/inputflinger/tests/InputClassifier_test.cpp
+++ b/services/inputflinger/tests/InputClassifier_test.cpp
@@ -29,20 +29,20 @@
 
 static NotifyMotionArgs generateBasicMotionArgs() {
     // Create a basic motion event for testing
-    constexpr size_t pointerCount = 1;
-    PointerProperties properties[pointerCount];
-    properties[0].id = 0;
-    properties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    PointerProperties properties;
+    properties.id = 0;
+    properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
 
-    PointerCoords coords[pointerCount];
-    coords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 1);
-    coords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
     static constexpr nsecs_t downTime = 2;
     NotifyMotionArgs motionArgs(1/*sequenceNum*/, downTime/*eventTime*/, 3/*deviceId*/,
             AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, 4/*policyFlags*/, AMOTION_EVENT_ACTION_DOWN,
             0/*actionButton*/, 0/*flags*/, AMETA_NONE, 0/*buttonState*/, MotionClassification::NONE,
             AMOTION_EVENT_EDGE_FLAG_NONE, 5/*deviceTimestamp*/,
-            0/*pointerCount*/, properties, coords, 0/*xPrecision*/, 0/*yPrecision*/,
+            1/*pointerCount*/, &properties, &coords, 0/*xPrecision*/, 0/*yPrecision*/,
             downTime, {}/*videoFrames*/);
     return motionArgs;
 }
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 7cd9e49..e4179ee 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -392,10 +392,9 @@
 void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
     // Add this buffer from our internal queue tracker
     { // Autolock scope
-        if (mFlinger->mUse90Hz && mFlinger->mUseSmart90ForVideo) {
-            // Report the requested present time to the Scheduler, if the feature is turned on.
-            mFlinger->mScheduler->addFramePresentTimeForLayer(item.mTimestamp,
-                                                              item.mIsAutoTimestamp, mName.c_str());
+        if (mFlinger->mUseSmart90ForVideo) {
+            // Report mApi ID for each layer.
+            mFlinger->mScheduler->addNativeWindowApi(item.mApi);
         }
 
         Mutex::Autolock lock(mQueueItemLock);
diff --git a/services/surfaceflinger/Scheduler/DispSync.cpp b/services/surfaceflinger/Scheduler/DispSync.cpp
index 665179e..1eccf9a 100644
--- a/services/surfaceflinger/Scheduler/DispSync.cpp
+++ b/services/surfaceflinger/Scheduler/DispSync.cpp
@@ -43,6 +43,7 @@
 namespace android {
 
 DispSync::~DispSync() = default;
+DispSync::Callback::~Callback() = default;
 
 namespace impl {
 
diff --git a/services/surfaceflinger/Scheduler/DispSync.h b/services/surfaceflinger/Scheduler/DispSync.h
index 4a90f10..f629697 100644
--- a/services/surfaceflinger/Scheduler/DispSync.h
+++ b/services/surfaceflinger/Scheduler/DispSync.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_DISPSYNC_H
-#define ANDROID_DISPSYNC_H
+#pragma once
 
 #include <stddef.h>
 
@@ -35,10 +34,16 @@
 public:
     class Callback {
     public:
-        virtual ~Callback() = default;
+        Callback() = default;
+        virtual ~Callback();
         virtual void onDispSyncEvent(nsecs_t when) = 0;
+
+    protected:
+        Callback(Callback const&) = delete;
+        Callback& operator=(Callback const&) = delete;
     };
 
+    DispSync() = default;
     virtual ~DispSync();
 
     virtual void reset() = 0;
@@ -57,6 +62,10 @@
     virtual nsecs_t expectedPresentTime() = 0;
 
     virtual void dump(std::string& result) const = 0;
+
+protected:
+    DispSync(DispSync const&) = delete;
+    DispSync& operator=(DispSync const&) = delete;
 };
 
 namespace impl {
@@ -239,5 +248,3 @@
 } // namespace impl
 
 } // namespace android
-
-#endif // ANDROID_DISPSYNC_H
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index e05c985..0ba6cf9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -28,6 +28,7 @@
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
 #include <cutils/properties.h>
+#include <system/window.h>
 #include <ui/DisplayStatInfo.h>
 #include <utils/Timers.h>
 #include <utils/Trace.h>
@@ -67,9 +68,14 @@
     mPrimaryDispSync = std::move(primaryDispSync);
     mEventControlThread = std::make_unique<impl::EventControlThread>(function);
 
+    mSetIdleTimerMs = set_idle_timer_ms(0);
+
     char value[PROPERTY_VALUE_MAX];
     property_get("debug.sf.set_idle_timer_ms", value, "0");
-    mSetIdleTimerMs = atoi(value);
+    int int_value = atoi(value);
+    if (int_value) {
+        mSetIdleTimerMs = atoi(value);
+    }
 
     if (mSetIdleTimerMs > 0) {
         mIdleTimer =
@@ -291,21 +297,29 @@
     mPrimaryDispSync->dump(result);
 }
 
-void Scheduler::addFramePresentTimeForLayer(const nsecs_t framePresentTime, bool isAutoTimestamp,
-                                            const std::string layerName) {
-    // This is V1 logic. It calculates the average FPS based on the timestamp frequency
-    // regardless of which layer the timestamp came from.
-    // For now, the averages and FPS are recorded in the systrace.
-    determineTimestampAverage(isAutoTimestamp, framePresentTime);
-
-    // This is V2 logic. It calculates the average and median timestamp difference based on the
-    // individual layer history. The results are recorded in the systrace.
-    determineLayerTimestampStats(layerName, framePresentTime);
+void Scheduler::addNativeWindowApi(int apiId) {
+    std::lock_guard<std::mutex> lock(mWindowApiHistoryLock);
+    mWindowApiHistory[mApiHistoryCounter] = apiId;
+    mApiHistoryCounter++;
+    mApiHistoryCounter = mApiHistoryCounter % scheduler::ARRAY_SIZE;
 }
 
-void Scheduler::incrementFrameCounter() {
-    std::lock_guard<std::mutex> lock(mLayerHistoryLock);
-    mLayerHistory.incrementCounter();
+void Scheduler::updateFpsBasedOnNativeWindowApi() {
+    int mode;
+    {
+        std::lock_guard<std::mutex> lock(mWindowApiHistoryLock);
+        mode = scheduler::calculate_mode(mWindowApiHistory);
+    }
+    ATRACE_INT("NativeWindowApiMode", mode);
+
+    if (mode == NATIVE_WINDOW_API_MEDIA) {
+        // TODO(b/127365162): These callback names are not accurate anymore. Update.
+        mediaChangeRefreshRate(MediaFeatureState::MEDIA_PLAYING);
+        ATRACE_INT("DetectedVideo", 1);
+    } else {
+        mediaChangeRefreshRate(MediaFeatureState::MEDIA_OFF);
+        ATRACE_INT("DetectedVideo", 0);
+    }
 }
 
 void Scheduler::setChangeRefreshRateCallback(
@@ -323,85 +337,6 @@
     }
 }
 
-void Scheduler::determineLayerTimestampStats(const std::string layerName,
-                                             const nsecs_t framePresentTime) {
-    std::vector<int64_t> differencesMs;
-    std::string differencesText = "";
-    {
-        std::lock_guard<std::mutex> lock(mLayerHistoryLock);
-        mLayerHistory.insert(layerName, framePresentTime);
-
-        // Traverse through the layer history, and determine the differences in present times.
-        nsecs_t newestPresentTime = framePresentTime;
-        for (int i = 1; i < mLayerHistory.getSize(); i++) {
-            std::unordered_map<std::string, nsecs_t> layers = mLayerHistory.get(i);
-            for (auto layer : layers) {
-                if (layer.first != layerName) {
-                    continue;
-                }
-                int64_t differenceMs = (newestPresentTime - layer.second) / 1000000;
-                // Dismiss noise.
-                if (differenceMs > 10 && differenceMs < 60) {
-                    differencesMs.push_back(differenceMs);
-                }
-                IF_ALOGV() { differencesText += (std::to_string(differenceMs) + " "); }
-                newestPresentTime = layer.second;
-            }
-        }
-    }
-    ALOGV("Layer %s timestamp intervals: %s", layerName.c_str(), differencesText.c_str());
-
-    if (!differencesMs.empty()) {
-        // Mean/Average is a good indicator for when 24fps videos are playing, because the frames
-        // come in 33, and 49 ms intervals with occasional 41ms.
-        const int64_t meanMs = scheduler::calculate_mean(differencesMs);
-        const auto tagMean = "TimestampMean_" + layerName;
-        ATRACE_INT(tagMean.c_str(), meanMs);
-
-        // Mode and median are good indicators for 30 and 60 fps videos, because the majority of
-        // frames come in 16, or 33 ms intervals.
-        const auto tagMedian = "TimestampMedian_" + layerName;
-        ATRACE_INT(tagMedian.c_str(), scheduler::calculate_median(&differencesMs));
-
-        const auto tagMode = "TimestampMode_" + layerName;
-        ATRACE_INT(tagMode.c_str(), scheduler::calculate_mode(differencesMs));
-    }
-}
-
-void Scheduler::determineTimestampAverage(bool isAutoTimestamp, const nsecs_t framePresentTime) {
-    ATRACE_INT("AutoTimestamp", isAutoTimestamp);
-
-    // Video does not have timestamp automatically set, so we discard timestamps that are
-    // coming in from other sources for now.
-    if (isAutoTimestamp) {
-        return;
-    }
-    int64_t differenceMs = (framePresentTime - mPreviousFrameTimestamp) / 1000000;
-    mPreviousFrameTimestamp = framePresentTime;
-
-    if (differenceMs < 10 || differenceMs > 100) {
-        // Dismiss noise.
-        return;
-    }
-    ATRACE_INT("TimestampDiff", differenceMs);
-
-    mTimeDifferences[mCounter % scheduler::ARRAY_SIZE] = differenceMs;
-    mCounter++;
-    int64_t mean = scheduler::calculate_mean(mTimeDifferences);
-    ATRACE_INT("AutoTimestampMean", mean);
-
-    // TODO(b/113612090): This are current numbers from trial and error while running videos
-    // from YouTube at 24, 30, and 60 fps.
-    if (mean > 14 && mean < 18) {
-        ATRACE_INT("MediaFPS", 60);
-    } else if (mean > 31 && mean < 34) {
-        ATRACE_INT("MediaFPS", 30);
-        return;
-    } else if (mean > 39 && mean < 42) {
-        ATRACE_INT("MediaFPS", 24);
-    }
-}
-
 void Scheduler::resetIdleTimer() {
     if (mIdleTimer) {
         mIdleTimer->reset();
@@ -409,21 +344,15 @@
 }
 
 void Scheduler::resetTimerCallback() {
-    std::lock_guard<std::mutex> lock(mCallbackLock);
-    if (mChangeRefreshRateCallback) {
-        // We do not notify the applications about config changes when idle timer is reset.
-        mChangeRefreshRateCallback(RefreshRateType::PERFORMANCE, ConfigEvent::None);
-        ATRACE_INT("ExpiredIdleTimer", 0);
-    }
+    // We do not notify the applications about config changes when idle timer is reset.
+    timerChangeRefreshRate(IdleTimerState::RESET);
+    ATRACE_INT("ExpiredIdleTimer", 0);
 }
 
 void Scheduler::expiredTimerCallback() {
-    std::lock_guard<std::mutex> lock(mCallbackLock);
-    if (mChangeRefreshRateCallback) {
-        // We do not notify the applications about config changes when idle timer expires.
-        mChangeRefreshRateCallback(RefreshRateType::DEFAULT, ConfigEvent::None);
-        ATRACE_INT("ExpiredIdleTimer", 1);
-    }
+    // We do not notify the applications about config changes when idle timer expires.
+    timerChangeRefreshRate(IdleTimerState::EXPIRED);
+    ATRACE_INT("ExpiredIdleTimer", 1);
 }
 
 std::string Scheduler::doDump() {
@@ -432,4 +361,46 @@
     return stream.str();
 }
 
+void Scheduler::mediaChangeRefreshRate(MediaFeatureState mediaFeatureState) {
+    // Default playback for media is DEFAULT when media is on.
+    RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
+    ConfigEvent configEvent = ConfigEvent::None;
+
+    {
+        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        mCurrentMediaFeatureState = mediaFeatureState;
+        // Only switch to PERFORMANCE if idle timer was reset, when turning
+        // media off. If the timer is IDLE, stay at DEFAULT.
+        if (mediaFeatureState == MediaFeatureState::MEDIA_OFF &&
+            mCurrentIdleTimerState == IdleTimerState::RESET) {
+            refreshRateType = RefreshRateType::PERFORMANCE;
+        }
+    }
+    changeRefreshRate(refreshRateType, configEvent);
+}
+
+void Scheduler::timerChangeRefreshRate(IdleTimerState idleTimerState) {
+    RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
+    ConfigEvent configEvent = ConfigEvent::None;
+
+    {
+        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        mCurrentIdleTimerState = idleTimerState;
+        // Only switch to PERFOMANCE if the idle timer was reset, and media is
+        // not playing. Otherwise, stay at DEFAULT.
+        if (idleTimerState == IdleTimerState::RESET &&
+            mCurrentMediaFeatureState == MediaFeatureState::MEDIA_OFF) {
+            refreshRateType = RefreshRateType::PERFORMANCE;
+        }
+    }
+    changeRefreshRate(refreshRateType, configEvent);
+}
+
+void Scheduler::changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent) {
+    std::lock_guard<std::mutex> lock(mCallbackLock);
+    if (mChangeRefreshRateCallback) {
+        mChangeRefreshRateCallback(refreshRateType, configEvent);
+    }
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index f8667d0..2582c93 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -141,13 +141,16 @@
     void addPresentFence(const std::shared_ptr<FenceTime>& fenceTime);
     void setIgnorePresentFences(bool ignore);
     nsecs_t expectedPresentTime();
-    // Adds the present time for given layer to the history of present times.
-    void addFramePresentTimeForLayer(const nsecs_t framePresentTime, bool isAutoTimestamp,
-                                     const std::string layerName);
-    // Increments counter in the layer history to indicate that SF has started a new frame.
-    void incrementFrameCounter();
+    // apiId indicates the API (NATIVE_WINDOW_API_xxx) that queues the buffer.
+    // TODO(b/123956502): Remove this call with V1 go/content-fps-detection-in-scheduler.
+    void addNativeWindowApi(int apiId);
+    // Updates FPS based on the most occured request for Native Window API.
+    void updateFpsBasedOnNativeWindowApi();
     // Callback that gets invoked when Scheduler wants to change the refresh rate.
     void setChangeRefreshRateCallback(const ChangeRefreshRateCallback& changeRefreshRateCallback);
+
+    // Returns whether idle timer is enabled or not
+    bool isIdleTimerEnabled() { return mSetIdleTimerMs > 0; }
     // Returns relevant information about Scheduler for dumpsys purposes.
     std::string doDump();
 
@@ -162,17 +165,16 @@
 private:
     friend class TestableScheduler;
 
+    // In order to make sure that the features don't override themselves, we need a state machine
+    // to keep track which feature requested the config change.
+    enum class MediaFeatureState { MEDIA_PLAYING, MEDIA_OFF };
+    enum class IdleTimerState { EXPIRED, RESET };
+
     // Creates a connection on the given EventThread and forwards the given callbacks.
     sp<EventThreadConnection> createConnectionInternal(EventThread*, ResyncCallback&&);
 
     nsecs_t calculateAverage() const;
     void updateFrameSkipping(const int64_t skipCount);
-    // Collects the statistical mean (average) and median between timestamp
-    // intervals for each frame for each layer.
-    void determineLayerTimestampStats(const std::string layerName, const nsecs_t framePresentTime);
-    // Collects the average difference between timestamps for each frame regardless
-    // of which layer the timestamp came from.
-    void determineTimestampAverage(bool isAutoTimestamp, const nsecs_t framePresentTime);
     // Function that resets the idle timer.
     void resetIdleTimer();
     // Function that is called when the timer resets.
@@ -181,6 +183,12 @@
     void expiredTimerCallback();
     // Sets vsync period.
     void setVsyncPeriod(const nsecs_t period);
+    // Media feature's function to change the refresh rate.
+    void mediaChangeRefreshRate(MediaFeatureState mediaFeatureState);
+    // Idle timer feature's function to change the refresh rate.
+    void timerChangeRefreshRate(IdleTimerState idleTimerState);
+    // Acquires a lock and calls the ChangeRefreshRateCallback() with given parameters.
+    void changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent);
 
     // If fences from sync Framework are supported.
     const bool mHasSyncFramework;
@@ -214,10 +222,13 @@
     std::array<int64_t, scheduler::ARRAY_SIZE> mTimeDifferences{};
     size_t mCounter = 0;
 
-    // DetermineLayerTimestampStats is called from BufferQueueLayer::onFrameAvailable which
-    // can run on any thread, and cause failure.
-    std::mutex mLayerHistoryLock;
-    LayerHistory mLayerHistory GUARDED_BY(mLayerHistoryLock);
+    // The following few fields follow native window api bits that come with buffers. If there are
+    // more buffers with NATIVE_WINDOW_API_MEDIA we render at 60Hz, otherwise we render at 90Hz.
+    // There is not dependency on timestamp for V0.
+    // TODO(b/123956502): Remove this when more robust logic for content fps detection is developed.
+    std::mutex mWindowApiHistoryLock;
+    std::array<int, scheduler::ARRAY_SIZE> mWindowApiHistory GUARDED_BY(mWindowApiHistoryLock);
+    int64_t mApiHistoryCounter = 0;
 
     // Timer that records time between requests for next vsync. If the time is higher than a given
     // interval, a callback is fired. Set this variable to >0 to use this feature.
@@ -226,6 +237,13 @@
 
     std::mutex mCallbackLock;
     ChangeRefreshRateCallback mChangeRefreshRateCallback GUARDED_BY(mCallbackLock);
+
+    // In order to make sure that the features don't override themselves, we need a state machine
+    // to keep track which feature requested the config change.
+    std::mutex mFeatureStateLock;
+    MediaFeatureState mCurrentMediaFeatureState GUARDED_BY(mFeatureStateLock) =
+            MediaFeatureState::MEDIA_OFF;
+    IdleTimerState mCurrentIdleTimerState GUARDED_BY(mFeatureStateLock) = IdleTimerState::RESET;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp b/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
index 191022d..fb5414f 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
@@ -34,23 +34,5 @@
     return v->at(n);
 }
 
-int64_t calculate_mode(const std::vector<int64_t>& v) {
-    if (v.empty()) {
-        return 0;
-    }
-
-    // Create a map with all the counts for the indivicual values in the vector.
-    std::unordered_map<int64_t, int64_t> counts;
-    for (int64_t value : v) {
-        counts[value]++;
-    }
-
-    // Sort the map, and return the number with the highest count. If two numbers have
-    // the same count, first one is returned.
-    using ValueType = const decltype(counts)::value_type&;
-    const auto compareCounts = [](ValueType l, ValueType r) { return l.second <= r.second; };
-    return std::max_element(counts.begin(), counts.end(), compareCounts)->first;
-}
-
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.h b/services/surfaceflinger/Scheduler/SchedulerUtils.h
index edd23de..9e6e8c7 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.h
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.h
@@ -18,6 +18,7 @@
 
 #include <cinttypes>
 #include <numeric>
+#include <unordered_map>
 #include <vector>
 
 namespace android {
@@ -45,7 +46,24 @@
 int64_t calculate_median(std::vector<int64_t>* v);
 
 // Calculates the statistical mode in the vector. Return 0 if the vector is empty.
-int64_t calculate_mode(const std::vector<int64_t>& v);
+template <typename T>
+auto calculate_mode(const T& v) {
+    if (v.empty()) {
+        return 0;
+    }
+
+    // Create a map with all the counts for the indivicual values in the vector.
+    std::unordered_map<int64_t, int> counts;
+    for (int64_t value : v) {
+        counts[value]++;
+    }
+
+    // Sort the map, and return the number with the highest count. If two numbers have
+    // the same count, first one is returned.
+    using ValueType = const decltype(counts)::value_type&;
+    const auto compareCounts = [](ValueType l, ValueType r) { return l.second <= r.second; };
+    return static_cast<int>(std::max_element(counts.begin(), counts.end(), compareCounts)->first);
+}
 
 } // namespace scheduler
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e445eb6..dba1f8e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -370,11 +370,13 @@
     auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize));
     mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize;
 
-    property_get("debug.sf.use_90Hz", value, "0");
-    mUse90Hz = atoi(value);
-
+    mUseSmart90ForVideo = use_smart_90_for_video(false);
     property_get("debug.sf.use_smart_90_for_video", value, "0");
-    mUseSmart90ForVideo = atoi(value);
+
+    int int_value = atoi(value);
+    if (int_value) {
+        mUseSmart90ForVideo = true;
+    }
 
     property_get("debug.sf.luma_sampling", value, "1");
     mLumaSampling = atoi(value);
@@ -563,12 +565,14 @@
         readPersistentProperties();
         mBootStage = BootStage::FINISHED;
 
-        // TODO(b/122905403): Once the display policy is completely integrated, this flag should go
-        // away and it should be controlled by flipping the switch in setting. The switch in
-        // settings should only be available to P19 devices, if they are opted into 90Hz fishfood.
-        // The boot must be complete before we can set the active config.
+        // set the refresh rate according to the policy
+        const auto displayId = getInternalDisplayIdLocked();
+        LOG_ALWAYS_FATAL_IF(!displayId);
 
-        if (mUse90Hz) {
+        const auto performanceRefreshRate =
+                mRefreshRateConfigs[*displayId]->getRefreshRate(RefreshRateType::PERFORMANCE);
+
+        if (isConfigAllowed(*displayId, performanceRefreshRate.configId)) {
             mPhaseOffsets->setRefreshRateType(
                     scheduler::RefreshRateConfigs::RefreshRateType::PERFORMANCE);
             setRefreshRateTo(RefreshRateType::PERFORMANCE, Scheduler::ConfigEvent::None);
@@ -699,7 +703,7 @@
         ALOGE("Run StartPropertySetThread failed!");
     }
 
-    if (mUse90Hz) {
+    if (mScheduler->isIdleTimerEnabled()) {
         mScheduler->setChangeRefreshRateCallback(
                 [this](RefreshRateType type, Scheduler::ConfigEvent event) {
                     Mutex::Autolock lock(mStateLock);
@@ -1584,15 +1588,16 @@
     ATRACE_CALL();
     switch (what) {
         case MessageQueue::INVALIDATE: {
+            if (mUseSmart90ForVideo) {
+                // This call is made each time SF wakes up and creates a new frame. It is part
+                // of video detection feature.
+                mScheduler->updateFpsBasedOnNativeWindowApi();
+            }
+
             if (performSetActiveConfig()) {
                 break;
             }
 
-            if (mUse90Hz && mUseSmart90ForVideo) {
-                // This call is made each time SF wakes up and creates a new frame. It is part
-                // of video detection feature.
-                mScheduler->incrementFrameCounter();
-            }
             bool frameMissed = mPreviousPresentFence != Fence::NO_FENCE &&
                     (mPreviousPresentFence->getStatus() == Fence::Status::Unsignaled);
             bool hwcFrameMissed = !mHadClientComposition && frameMissed;
@@ -4391,7 +4396,7 @@
                   "    present offset: %9" PRId64 " ns\t     VSYNC period: %9" PRId64 " ns\n\n",
                   dispSyncPresentTimeOffset, getVsyncPeriod());
 
-    StringAppendF(&result, "Scheduler enabled. 90Hz feature: %s\n", mUse90Hz ? "on" : "off");
+    StringAppendF(&result, "Scheduler enabled.");
     StringAppendF(&result, "+  Smart 90 for video detection: %s\n\n",
                   mUseSmart90ForVideo ? "on" : "off");
     mScheduler->dump(mAppConnectionHandle, result);
@@ -4745,6 +4750,7 @@
      */
     result.append("\nScheduler state:\n");
     result.append(mScheduler->doDump() + "\n");
+    StringAppendF(&result, "+  Smart video mode: %s\n\n", mUseSmart90ForVideo ? "on" : "off");
     result.append(mRefreshRateStats->doDump() + "\n");
 }
 
@@ -5658,6 +5664,18 @@
             }
         }
     }
+
+    // If idle timer and fps detection are disabled and we are in RefreshRateType::DEFAULT,
+    // there is no trigger to move to RefreshRateType::PERFORMANCE, even if it is an allowed.
+    if (!mScheduler->isIdleTimerEnabled() && !mUseSmart90ForVideo) {
+        const auto performanceRefreshRate =
+                mRefreshRateConfigs[*displayId]->getRefreshRate(RefreshRateType::PERFORMANCE);
+        if (isConfigAllowed(*displayId, performanceRefreshRate.configId)) {
+            mPhaseOffsets->setRefreshRateType(
+                    scheduler::RefreshRateConfigs::RefreshRateType::PERFORMANCE);
+            setRefreshRateTo(RefreshRateType::PERFORMANCE, Scheduler::ConfigEvent::Changed);
+        }
+    }
 }
 
 status_t SurfaceFlinger::setAllowedDisplayConfigs(const android::sp<android::IBinder>& displayToken,
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index deffe9d..8de1e97 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1100,7 +1100,6 @@
     /* ------------------------------------------------------------------------
      * Scheduler
      */
-    bool mUse90Hz = false;
     bool mUseSmart90ForVideo = false;
     std::unique_ptr<Scheduler> mScheduler;
     sp<Scheduler::ConnectionHandle> mAppConnectionHandle;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index cb9a6c0..e130511 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -209,6 +209,22 @@
     return static_cast<int32_t>(defaultValue);
 }
 
+int32_t set_idle_timer_ms(int32_t defaultValue) {
+    auto temp = SurfaceFlingerProperties::set_idle_timer_ms();
+    if (temp.has_value()) {
+        return *temp;
+    }
+    return defaultValue;
+}
+
+bool use_smart_90_for_video(bool defaultValue) {
+    auto temp = SurfaceFlingerProperties::use_smart_90_for_video();
+    if (temp.has_value()) {
+        return *temp;
+    }
+    return defaultValue;
+}
+
 #define DISPLAY_PRIMARY_SIZE 3
 
 constexpr float kSrgbRedX = 0.4123f;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 9917683..6f90117 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -55,6 +55,10 @@
 int32_t wcg_composition_pixel_format(
         android::hardware::graphics::common::V1_2::PixelFormat defaultValue);
 
+int32_t set_idle_timer_ms(int32_t defaultValue);
+
+bool use_smart_90_for_video(bool defaultValue);
+
 android::ui::DisplayPrimaries getDisplayNativePrimaries();
 } // namespace sysprop
 } // namespace android
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 429636b..fe6dc93 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -286,3 +286,24 @@
     access: Readonly
     prop_name: "ro.surface_flinger.display_primary_white"
 }
+
+# setIdleTimerMs indicates what is considered a timeout in milliseconds for Scheduler. This value is
+# used by the Scheduler to trigger inactivity callbacks that will switch the display to a lower
+# refresh rate. Setting this property to 0 means there is no timer.
+prop {
+    api_name: "set_idle_timer_ms"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.surface_flinger.set_idle_timer_ms"
+}
+
+# useSmart90ForVideo indicates whether Scheduler should detect content FPS, and try to adjust the
+# screen refresh rate based on that.
+prop {
+    api_name: "use_smart_90_for_video"
+    type: Boolean
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.surface_flinger.use_smart_90_for_video"
+}
diff --git a/services/surfaceflinger/tests/SurfaceFlinger_test.filter b/services/surfaceflinger/tests/SurfaceFlinger_test.filter
index 91999ae..be862c9 100644
--- a/services/surfaceflinger/tests/SurfaceFlinger_test.filter
+++ b/services/surfaceflinger/tests/SurfaceFlinger_test.filter
@@ -1,5 +1,5 @@
 {
         "presubmit": {
-            "filter": "CredentialsTest.*:SurfaceFlingerStress.*:SurfaceInterceptorTest.*:LayerTransactionTest.*:LayerTypeTransactionTest.*:LayerUpdateTest.*:GeometryLatchingTest.*:CropLatchingTest.*:ChildLayerTest.*:ScreenCaptureTest.*:ScreenCaptureChildOnlyTest.*:DereferenceSurfaceControlTest.*:BoundlessLayerTest.*"
+            "filter": "CredentialsTest.*:SurfaceFlingerStress.*:SurfaceInterceptorTest.*:LayerTransactionTest.*:LayerTypeTransactionTest.*:LayerUpdateTest.*:GeometryLatchingTest.*:CropLatchingTest.*:ChildLayerTest.*:ScreenCaptureTest.*:ScreenCaptureChildOnlyTest.*:DereferenceSurfaceControlTest.*:BoundlessLayerTest.*:MultiDisplayLayerBoundsTest.*"
         }
 }
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 34cdff7..319e01c 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -198,12 +198,15 @@
 class ScreenCapture : public RefBase {
 public:
     static void captureScreen(std::unique_ptr<ScreenCapture>* sc) {
+        captureScreen(sc, SurfaceComposerClient::getInternalDisplayToken());
+    }
+
+    static void captureScreen(std::unique_ptr<ScreenCapture>* sc, sp<IBinder> displayToken) {
         const auto sf = ComposerService::getComposerService();
-        const auto token = sf->getInternalDisplayToken();
         SurfaceComposerClient::Transaction().apply(true);
 
         sp<GraphicBuffer> outBuffer;
-        ASSERT_EQ(NO_ERROR, sf->captureScreen(token, &outBuffer, Rect(), 0, 0, false));
+        ASSERT_EQ(NO_ERROR, sf->captureScreen(displayToken, &outBuffer, Rect(), 0, 0, false));
         *sc = std::make_unique<ScreenCapture>(outBuffer);
     }
 
@@ -482,6 +485,12 @@
         return screenshot;
     }
 
+    void asTransaction(const std::function<void(Transaction&)>& exec) {
+        Transaction t;
+        exec(t);
+        t.apply(true);
+    }
+
     static status_t getBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence) {
         static BufferGenerator bufferGenerator;
         return bufferGenerator.get(outBuffer, outFence);
@@ -4086,11 +4095,6 @@
         fillSurfaceRGBA8(mSyncSurfaceControl, 31, 31, 31);
     }
 
-    void asTransaction(const std::function<void(Transaction&)>& exec) {
-        Transaction t;
-        exec(t);
-        t.apply(true);
-    }
 
     sp<SurfaceControl> mBGSurfaceControl;
     sp<SurfaceControl> mFGSurfaceControl;
@@ -5421,4 +5425,102 @@
     }
 }
 
+class MultiDisplayLayerBoundsTest : public LayerTransactionTest {
+protected:
+    virtual void SetUp() {
+        LayerTransactionTest::SetUp();
+        ASSERT_EQ(NO_ERROR, mClient->initCheck());
+
+        mMainDisplay = SurfaceComposerClient::getInternalDisplayToken();
+        SurfaceComposerClient::getDisplayInfo(mMainDisplay, &mMainDisplayInfo);
+
+        sp<IGraphicBufferConsumer> consumer;
+        BufferQueue::createBufferQueue(&mProducer, &consumer);
+        consumer->setConsumerName(String8("Virtual disp consumer"));
+        consumer->setDefaultBufferSize(mMainDisplayInfo.w, mMainDisplayInfo.h);
+    }
+
+    virtual void TearDown() {
+        SurfaceComposerClient::destroyDisplay(mVirtualDisplay);
+        LayerTransactionTest::TearDown();
+        mColorLayer = 0;
+    }
+
+    void createDisplay(const Rect& layerStackRect, uint32_t layerStack) {
+        mVirtualDisplay =
+                SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
+        asTransaction([&](Transaction& t) {
+            t.setDisplaySurface(mVirtualDisplay, mProducer);
+            t.setDisplayLayerStack(mVirtualDisplay, layerStack);
+            t.setDisplayProjection(mVirtualDisplay, mMainDisplayInfo.orientation, layerStackRect,
+                                   Rect(mMainDisplayInfo.w, mMainDisplayInfo.h));
+        });
+    }
+
+    void createColorLayer(uint32_t layerStack) {
+        mColorLayer =
+                createSurface(mClient, "ColorLayer", 0 /* buffer width */, 0 /* buffer height */,
+                              PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceColor);
+        ASSERT_TRUE(mColorLayer != nullptr);
+        ASSERT_TRUE(mColorLayer->isValid());
+        asTransaction([&](Transaction& t) {
+            t.setLayerStack(mColorLayer, layerStack);
+            t.setCrop_legacy(mColorLayer, Rect(0, 0, 30, 40));
+            t.setLayer(mColorLayer, INT32_MAX - 2);
+            t.setColor(mColorLayer,
+                       half3{mExpectedColor.r / 255.0f, mExpectedColor.g / 255.0f,
+                             mExpectedColor.b / 255.0f});
+            t.show(mColorLayer);
+        });
+    }
+
+    DisplayInfo mMainDisplayInfo;
+    sp<IBinder> mMainDisplay;
+    sp<IBinder> mVirtualDisplay;
+    sp<IGraphicBufferProducer> mProducer;
+    sp<SurfaceControl> mColorLayer;
+    Color mExpectedColor = {63, 63, 195, 255};
+};
+
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInVirtualDisplay) {
+    createDisplay({mMainDisplayInfo.viewportW, mMainDisplayInfo.viewportH}, 1 /* layerStack */);
+    createColorLayer(1 /* layerStack */);
+
+    asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
+
+    // Verify color layer does not render on main display.
+    std::unique_ptr<ScreenCapture> sc;
+    ScreenCapture::captureScreen(&sc, mMainDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), {0, 0, 0, 255});
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+
+    // Verify color layer renders correctly on virtual display.
+    ScreenCapture::captureScreen(&sc, mVirtualDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(1, 1, 9, 9), {0, 0, 0, 0});
+}
+
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInMirroredVirtualDisplay) {
+    // Create a display and set its layer stack to the main display's layer stack so
+    // the contents of the main display are mirrored on to the virtual display.
+
+    // Assumption here is that the new mirrored display has the same viewport as the
+    // primary display that it is mirroring.
+    createDisplay({mMainDisplayInfo.viewportW, mMainDisplayInfo.viewportH}, 0 /* layerStack */);
+    createColorLayer(0 /* layerStack */);
+
+    asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
+
+    // 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
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 79b5ca0..13059e8 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -341,6 +341,8 @@
         // still be referenced by something despite our best efforts to destroy
         // it after each test is done.
         mutableDisplays().clear();
+        mutableCurrentState().displays.clear();
+        mutableDrawingState().displays.clear();
         mutableEventQueue().reset();
         mutableInterceptor().reset();
         mutableScheduler().reset();