Merge "Create separate api for reconciling per-sdk storage" into tm-dev
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 8e23eb87..ec4c7c1 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -182,7 +182,8 @@
              static_cast<uint32_t>(mPendingTransactions.size()));
     SurfaceComposerClient::Transaction t;
     mergePendingTransactions(&t, std::numeric_limits<uint64_t>::max() /* frameNumber */);
-    t.setApplyToken(mApplyToken).apply();
+    // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+    t.setApplyToken(mApplyToken).apply(false, true);
 }
 
 void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height,
@@ -230,7 +231,8 @@
         }
     }
     if (applyTransaction) {
-        t.setApplyToken(mApplyToken).apply();
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t.setApplyToken(mApplyToken).apply(false, true);
     }
 }
 
@@ -551,7 +553,13 @@
 
     mergePendingTransactions(t, bufferItem.mFrameNumber);
     if (applyTransaction) {
-        t->setApplyToken(mApplyToken).apply();
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t->setApplyToken(mApplyToken).apply(false, true);
+        mAppliedLastTransaction = true;
+        mLastAppliedFrameNumber = bufferItem.mFrameNumber;
+    } else {
+        t->setBufferHasBarrier(mSurfaceControl, mLastAppliedFrameNumber);
+        mAppliedLastTransaction = false;
     }
 
     BQA_LOGV("acquireNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64
@@ -857,7 +865,8 @@
 
     SurfaceComposerClient::Transaction t;
     mergePendingTransactions(&t, frameNumber);
-    t.setApplyToken(mApplyToken).apply();
+    // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+    t.setApplyToken(mApplyToken).apply(false, true);
 }
 
 void BLASTBufferQueue::mergePendingTransactions(SurfaceComposerClient::Transaction* t,
@@ -1050,7 +1059,8 @@
                  static_cast<uint32_t>(mPendingTransactions.size()));
         SurfaceComposerClient::Transaction t;
         mergePendingTransactions(&t, std::numeric_limits<uint64_t>::max() /* frameNumber */);
-        t.setApplyToken(mApplyToken).apply();
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t.setApplyToken(mApplyToken).apply(false, true);
     }
 
     // Clear sync states
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 5ab0abc..5532c6e 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -111,7 +111,13 @@
 
         SAFE_PARCEL(data.writeUint64, transactionId);
 
-        return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE, data, &reply);
+        if (flags & ISurfaceComposer::eOneWay) {
+            return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
+                    data, &reply, IBinder::FLAG_ONEWAY);
+        } else {
+            return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
+                    data, &reply);
+        }
     }
 
     void bootFinished() override {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 6944d38..338ff11 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -796,6 +796,8 @@
 
     SAFE_PARCEL(output->writeStrongBinder, cachedBuffer.token.promote());
     SAFE_PARCEL(output->writeUint64, cachedBuffer.id);
+    SAFE_PARCEL(output->writeBool, hasBarrier);
+    SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
 
     return NO_ERROR;
 }
@@ -832,6 +834,9 @@
     cachedBuffer.token = tmpBinder;
     SAFE_PARCEL(input->readUint64, &cachedBuffer.id);
 
+    SAFE_PARCEL(input->readBool, &hasBarrier);
+    SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
+
     return NO_ERROR;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 26ccda5..27856ce 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -930,7 +930,7 @@
     }
 }
 
-status_t SurfaceComposerClient::Transaction::apply(bool synchronous) {
+status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay) {
     if (mStatus != NO_ERROR) {
         return mStatus;
     }
@@ -984,6 +984,14 @@
     if (mAnimation) {
         flags |= ISurfaceComposer::eAnimation;
     }
+    if (oneWay) {
+      if (mForceSynchronous) {
+          ALOGE("Transaction attempted to set synchronous and one way at the same time"
+                " this is an invalid request. Synchronous will win for safety");
+      } else {
+          flags |= ISurfaceComposer::eOneWay;
+      }
+    }
 
     // If both mEarlyWakeupStart and mEarlyWakeupEnd are set
     // it is equivalent for none
@@ -1399,6 +1407,18 @@
     return bufferData;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferHasBarrier(
+        const sp<SurfaceControl>& sc, uint64_t barrierFrameNumber) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->bufferData->hasBarrier = true;
+    s->bufferData->barrierFrameNumber = barrierFrameNumber;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
         const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
         const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& frameNumber,
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 80bd638..2312a8c 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -51,11 +51,11 @@
 }
 
 bool WindowInfo::isSpy() const {
-    return inputFeatures.test(Feature::SPY);
+    return inputConfig.test(InputConfig::SPY);
 }
 
 bool WindowInfo::interceptsStylus() const {
-    return inputFeatures.test(Feature::INTERCEPTS_STYLUS);
+    return inputConfig.test(InputConfig::INTERCEPTS_STYLUS);
 }
 
 bool WindowInfo::overlaps(const WindowInfo* other) const {
@@ -73,8 +73,7 @@
             info.touchableRegion.hasSameRects(touchableRegion) &&
             info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
             info.ownerUid == ownerUid && info.packageName == packageName &&
-            info.inputFeatures == inputFeatures && info.inputConfig == inputConfig &&
-            info.displayId == displayId &&
+            info.inputConfig == inputConfig && info.displayId == displayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
             info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType &&
             info.layoutParamsFlags == layoutParamsFlags;
@@ -92,7 +91,6 @@
     parcel->writeInt32(1);
 
     // Ensure that the size of the flags that we use is 32 bits for writing into the parcel.
-    static_assert(sizeof(inputFeatures) == 4u);
     static_assert(sizeof(inputConfig) == 4u);
 
     // clang-format off
@@ -120,7 +118,6 @@
         parcel->writeInt32(ownerPid) ?:
         parcel->writeInt32(ownerUid) ?:
         parcel->writeUtf8AsUtf16(packageName) ?:
-        parcel->writeInt32(inputFeatures.get()) ?:
         parcel->writeInt32(inputConfig.get()) ?:
         parcel->writeInt32(displayId) ?:
         applicationInfo.writeToParcel(parcel) ?:
@@ -148,12 +145,14 @@
         return status;
     }
 
-    layoutParamsFlags = Flags<Flag>(parcel->readInt32());
-    layoutParamsType = static_cast<Type>(parcel->readInt32());
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    int32_t touchOcclusionModeInt;
+    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt;
+    sp<IBinder> touchableRegionCropHandleSp;
+
     // clang-format off
-    status = parcel->readInt32(&frameLeft) ?:
+    status = parcel->readInt32(&lpFlags) ?:
+        parcel->readInt32(&lpType) ?:
+        parcel->readInt32(&frameLeft) ?:
         parcel->readInt32(&frameTop) ?:
         parcel->readInt32(&frameRight) ?:
         parcel->readInt32(&frameBottom) ?:
@@ -169,33 +168,28 @@
         parcel->readInt32(&touchOcclusionModeInt) ?:
         parcel->readInt32(&ownerPid) ?:
         parcel->readInt32(&ownerUid) ?:
-        parcel->readUtf8FromUtf16(&packageName);
-    // clang-format on
-
-    if (status != OK) {
-        return status;
-    }
-
-    touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
-
-    inputFeatures = Flags<Feature>(parcel->readInt32());
-    inputConfig = Flags<InputConfig>(parcel->readInt32());
-    // clang-format off
-    status = parcel->readInt32(&displayId) ?:
+        parcel->readUtf8FromUtf16(&packageName) ?:
+        parcel->readInt32(&inputConfigInt) ?:
+        parcel->readInt32(&displayId) ?:
         applicationInfo.readFromParcel(parcel) ?:
         parcel->read(touchableRegion) ?:
-        parcel->readBool(&replaceTouchableRegionWithCrop);
+        parcel->readBool(&replaceTouchableRegionWithCrop) ?:
+        parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
+        parcel->readNullableStrongBinder(&windowToken);
     // clang-format on
 
     if (status != OK) {
         return status;
     }
 
-    touchableRegionCropHandle = parcel->readStrongBinder();
+    layoutParamsFlags = Flags<Flag>(lpFlags);
+    layoutParamsType = static_cast<Type>(lpType);
     transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+    touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
+    inputConfig = Flags<InputConfig>(inputConfigInt);
+    touchableRegionCropHandle = touchableRegionCropHandleSp;
 
-    status = parcel->readNullableStrongBinder(&windowToken);
-    return status;
+    return OK;
 }
 
 // --- WindowInfoHandle ---
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 265ae24..65fc04d 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -254,6 +254,21 @@
     // surfacecontol. This is useful if the caller wants to synchronize the buffer scale with
     // additional scales in the hierarchy.
     bool mUpdateDestinationFrame GUARDED_BY(mMutex) = true;
+
+    // We send all transactions on our apply token over one-way binder calls to avoid blocking
+    // client threads. All of our transactions remain in order, since they are one-way binder calls
+    // from a single process, to a single interface. However once we give up a Transaction for sync
+    // we can start to have ordering issues. When we return from sync to normal frame production,
+    // we wait on the commit callback of sync frames ensuring ordering, however we don't want to
+    // wait on the commit callback for every normal frame (since even emitting them has a
+    // performance cost) this means we need a method to ensure frames are in order when switching
+    // from one-way application on our apply token, to application on some other apply token. We
+    // make use of setBufferHasBarrier to declare this ordering. This boolean simply tracks when we
+    // need to set this flag, notably only in the case where we are transitioning from a previous
+    // transaction applied by us (one way, may not yet have reached server) and an upcoming
+    // transaction that will be applied by some sync consumer.
+    bool mAppliedLastTransaction = false;
+    uint64_t mLastAppliedFrameNumber = 0;
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 4dfc383..0a3cc19 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -113,6 +113,7 @@
         // android.permission.ACCESS_SURFACE_FLINGER
         eEarlyWakeupStart = 0x08,
         eEarlyWakeupEnd = 0x10,
+        eOneWay = 0x20
     };
 
     enum VsyncSource {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 885b4ae..0f37dab 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -89,6 +89,8 @@
     // Used by BlastBufferQueue to forward the framenumber generated by the
     // graphics producer.
     uint64_t frameNumber = 0;
+    bool hasBarrier = false;
+    uint64_t barrierFrameNumber = 0;
 
     // Listens to when the buffer is safe to be released. This is used for blast
     // layers only. The callback includes a release fence as well as the graphic
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 6c79b5b..c8ac166 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -461,7 +461,7 @@
         // Clears the contents of the transaction without applying it.
         void clear();
 
-        status_t apply(bool synchronous = false);
+        status_t apply(bool synchronous = false, bool oneWay = false);
         // Merge another transaction in to this one, clearing other
         // as if it had been applied.
         Transaction& merge(Transaction&& other);
@@ -521,6 +521,27 @@
                                const std::optional<uint64_t>& frameNumber = std::nullopt,
                                ReleaseBufferCallback callback = nullptr);
         std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
+
+        /**
+         * If this transaction, has a a buffer set for the given SurfaceControl
+         * mark that buffer as ordered after a given barrierFrameNumber.
+         *
+         * SurfaceFlinger will refuse to apply this transaction until after
+         * the frame in barrierFrameNumber has been applied. This transaction may
+         * be applied in the same frame as the barrier buffer or after.
+         *
+         * This is only designed to be used to handle switches between multiple
+         * apply tokens, as explained in the comment for BLASTBufferQueue::mAppliedLastTransaction.
+         *
+         * Has to be called after setBuffer.
+         *
+         * WARNING:
+         * This API is very dangerous to the caller, as if you invoke it without
+         * a frameNumber you have not yet submitted, you can dead-lock your
+         * SurfaceControl's transaction queue.
+         */
+        Transaction& setBufferHasBarrier(const sp<SurfaceControl>& sc,
+                                         uint64_t barrierFrameNumber);
         Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
         Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index b9bffaa..ef0b98b 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -17,7 +17,7 @@
 #pragma once
 
 #include <android/gui/TouchOcclusionMode.h>
-#include <android/os/IInputConstants.h>
+#include <android/os/InputConfig.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
 #include <ftl/Flags.h>
@@ -132,49 +132,45 @@
         ftl_last = FIRST_SYSTEM_WINDOW + 15
     };
 
-    // This is a conversion of os::IInputConstants::InputFeature to an enum backed by an unsigned
-    // type. This indicates that they are flags, so it can be used with ftl/enum.h.
-    enum class Feature : uint32_t {
-        // clang-format off
-        NO_INPUT_CHANNEL =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::NO_INPUT_CHANNEL),
-        DISABLE_USER_ACTIVITY =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::DISABLE_USER_ACTIVITY),
-        DROP_INPUT =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::DROP_INPUT),
-        DROP_INPUT_IF_OBSCURED =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::DROP_INPUT_IF_OBSCURED),
-        SPY =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::SPY),
-        INTERCEPTS_STYLUS =
-                static_cast<uint32_t>(os::IInputConstants::InputFeature::INTERCEPTS_STYLUS),
-        // clang-format on
-    };
-
     // Flags used to determine configuration of this input window.
-    // Input windows can be configured with two sets of flags: InputFeature (WindowInfo::Feature
-    // defined above), and InputConfig. When adding a new configuration for an input window:
-    //   - If you are adding a new flag that's visible and accessible to apps, it should be added
-    //   as an InputFeature.
-    //   - If you are adding an internal behaviour that is used within the system or shell and is
-    //   not exposed to apps, it should be added as an InputConfig.
+    // This is a conversion of os::InputConfig to an enum backed by an unsigned
+    // type. This indicates that they are flags, so it can be used with ftl/enum.h.
     enum class InputConfig : uint32_t {
         // clang-format off
-        NONE                         = 0,
-        NOT_VISIBLE                  = 1 << 0,
-        NOT_FOCUSABLE                = 1 << 1,
-        NOT_TOUCHABLE                = 1 << 2,
-        PREVENT_SPLITTING            = 1 << 3,
-        DUPLICATE_TOUCH_TO_WALLPAPER = 1 << 4,
-        IS_WALLPAPER                 = 1 << 5,
-        PAUSE_DISPATCHING            = 1 << 6,
-        // This flag is set when the window is of a trusted type that is allowed to silently
-        // overlay other windows for the purpose of implementing the secure views feature.
-        // Trusted overlays, such as IME windows, can partly obscure other windows without causing
-        // motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
-        TRUSTED_OVERLAY              = 1 << 7,
-        WATCH_OUTSIDE_TOUCH          = 1 << 8,
-        SLIPPERY                     = 1 << 9,
+        DEFAULT =
+                static_cast<uint32_t>(os::InputConfig::DEFAULT),
+        NO_INPUT_CHANNEL =
+                static_cast<uint32_t>(os::InputConfig::NO_INPUT_CHANNEL),
+        NOT_VISIBLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_VISIBLE),
+        NOT_FOCUSABLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_FOCUSABLE),
+        NOT_TOUCHABLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_TOUCHABLE),
+        PREVENT_SPLITTING =
+                static_cast<uint32_t>(os::InputConfig::PREVENT_SPLITTING),
+        DUPLICATE_TOUCH_TO_WALLPAPER =
+                static_cast<uint32_t>(os::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER),
+        IS_WALLPAPER =
+                static_cast<uint32_t>(os::InputConfig::IS_WALLPAPER),
+        PAUSE_DISPATCHING =
+                static_cast<uint32_t>(os::InputConfig::PAUSE_DISPATCHING),
+        TRUSTED_OVERLAY =
+                static_cast<uint32_t>(os::InputConfig::TRUSTED_OVERLAY),
+        WATCH_OUTSIDE_TOUCH =
+                static_cast<uint32_t>(os::InputConfig::WATCH_OUTSIDE_TOUCH),
+        SLIPPERY =
+                static_cast<uint32_t>(os::InputConfig::SLIPPERY),
+        DISABLE_USER_ACTIVITY =
+                static_cast<uint32_t>(os::InputConfig::DISABLE_USER_ACTIVITY),
+        DROP_INPUT =
+                static_cast<uint32_t>(os::InputConfig::DROP_INPUT),
+        DROP_INPUT_IF_OBSCURED =
+                static_cast<uint32_t>(os::InputConfig::DROP_INPUT_IF_OBSCURED),
+        SPY =
+                static_cast<uint32_t>(os::InputConfig::SPY),
+        INTERCEPTS_STYLUS =
+                static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
         // clang-format on
     };
 
@@ -228,7 +224,6 @@
     int32_t ownerPid = -1;
     int32_t ownerUid = -1;
     std::string packageName;
-    Flags<Feature> inputFeatures;
     Flags<InputConfig> inputConfig;
     int32_t displayId = ADISPLAY_ID_NONE;
     InputApplicationInfo applicationInfo;
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index ff9bae2..c51b244 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -64,7 +64,6 @@
     i.ownerPid = 19;
     i.ownerUid = 24;
     i.packageName = "com.example.package";
-    i.inputFeatures = WindowInfo::Feature::DISABLE_USER_ACTIVITY;
     i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE;
     i.displayId = 34;
     i.replaceTouchableRegionWithCrop = true;
@@ -97,7 +96,6 @@
     ASSERT_EQ(i.ownerPid, i2.ownerPid);
     ASSERT_EQ(i.ownerUid, i2.ownerUid);
     ASSERT_EQ(i.packageName, i2.packageName);
-    ASSERT_EQ(i.inputFeatures, i2.inputFeatures);
     ASSERT_EQ(i.inputConfig, i2.inputConfig);
     ASSERT_EQ(i.displayId, i2.displayId);
     ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 930d819..606fe2a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/IInputConstants.aidl",
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
+        "android/os/InputConfig.aidl",
     ],
 }
 
@@ -79,11 +80,8 @@
         android: {
             srcs: [
                 "InputTransport.cpp",
-                "android/os/BlockUntrustedTouchesMode.aidl",
-                "android/os/IInputConstants.aidl",
                 "android/os/IInputFlinger.aidl",
-                "android/os/InputEventInjectionResult.aidl",
-                "android/os/InputEventInjectionSync.aidl",
+                ":inputconstants_aidl",
             ],
 
             export_shared_lib_headers: ["libbinder"],
@@ -119,6 +117,7 @@
                 "InputTransport.cpp",
                 "android/os/IInputConstants.aidl",
                 "android/os/IInputFlinger.aidl",
+                "android/os/InputConfig.aidl",
             ],
             static_libs: [
                 "libhostgraphics",
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 265cbf0..5ce10a4 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -47,55 +47,6 @@
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
-    @Backing(type="int")
-    enum InputFeature {
-        /**
-         * Does not construct an input channel for this window.  The channel will therefore
-         * be incapable of receiving input.
-         */
-        NO_INPUT_CHANNEL = 0x00000002,
-
-        /**
-         * When this window has focus, does not call user activity for all input events so
-         * the application will have to do it itself.  Should only be used by
-         * the keyguard and phone app.
-         *
-         * Should only be used by the keyguard and phone app.
-         */
-        DISABLE_USER_ACTIVITY = 0x00000004,
-
-        /**
-         * Internal flag used to indicate that input should be dropped on this window.
-         */
-        DROP_INPUT = 0x00000008,
-
-        /**
-         * Internal flag used to indicate that input should be dropped on this window if this window
-         * is obscured.
-         */
-        DROP_INPUT_IF_OBSCURED = 0x00000010,
-
-        /**
-         * An input spy window. This window will receive all pointer events within its touchable
-         * area, but will will not stop events from being sent to other windows below it in z-order.
-         * An input event will be dispatched to all spy windows above the top non-spy window at the
-         * event's coordinates.
-         */
-        SPY = 0x00000020,
-
-        /**
-         * When used with the window flag {@link #FLAG_NOT_TOUCHABLE}, this window will continue
-         * to receive events from a stylus device within its touchable region. All other pointer
-         * events, such as from a mouse or touchscreen, will be dispatched to the windows behind it.
-         *
-         * This input feature has no effect when the window flag {@link #FLAG_NOT_TOUCHABLE} is
-         * not set.
-         *
-         * The window must be a trusted overlay to use this input feature.
-         */
-        INTERCEPTS_STYLUS = 0x00000040,
-    }
-
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 }
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
new file mode 100644
index 0000000..6d1b396
--- /dev/null
+++ b/libs/input/android/os/InputConfig.aidl
@@ -0,0 +1,147 @@
+/**
+ * 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.
+ */
+
+package android.os;
+
+
+/**
+ * Input configurations flags used to determine the behavior of input windows.
+ * @hide
+ */
+@Backing(type="int")
+enum InputConfig {
+
+    /**
+     * The default InputConfig value with no flags set.
+     */
+    DEFAULT                      = 0,
+
+    /**
+     * Does not construct an input channel for this window.  The channel will therefore
+     * be incapable of receiving input.
+     */
+    NO_INPUT_CHANNEL             = 1 << 0,
+
+    /**
+     * Indicates that this input window is not visible, and thus will not be considered as
+     * an input target and will not obscure other windows.
+     */
+    NOT_VISIBLE                  = 1 << 1,
+
+    /**
+     * Indicates that this input window cannot be a focus target, and this will not
+     * receive any input events that can only be directed for the focused window, such
+     * as key events.
+     */
+    NOT_FOCUSABLE                = 1 << 2,
+
+    /**
+     * Indicates that this input window cannot receive any events directed at a
+     * specific location on the screen, such as touchscreen, mouse, and stylus events.
+     * The window will not be considered as a touch target, but can still obscure other
+     * windows.
+     */
+    NOT_TOUCHABLE                = 1 << 3,
+
+    /**
+     * Indicates that this window will not accept a touch event that is split between
+     * more than one window. When set:
+     *  - If this window receives a DOWN event with the first pointer, all successive
+     *    pointers that go down, regardless of their location on the screen, will be
+     *    directed to this window;
+     *  - If the DOWN event lands outside the touchable bounds of this window, no
+     *    successive pointers that go down, regardless of their location on the screen,
+     *    will be directed to this window.
+     */
+    PREVENT_SPLITTING            = 1 << 4,
+
+    /**
+     * Indicates that this window shows the wallpaper behind it, so all touch events
+     * that it receives should also be sent to the wallpaper.
+     */
+    DUPLICATE_TOUCH_TO_WALLPAPER = 1 << 5,
+
+    /** Indicates that this the wallpaper's input window. */
+    IS_WALLPAPER                 = 1 << 6,
+
+    /**
+     * Indicates that input events should not be dispatched to this window. When set,
+     * input events directed towards this window will simply be dropped, and will not
+     * be dispatched to windows behind it.
+     */
+    PAUSE_DISPATCHING            = 1 << 7,
+
+    /**
+     * This flag is set when the window is of a trusted type that is allowed to silently
+     * overlay other windows for the purpose of implementing the secure views feature.
+     * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+     * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+     */
+    TRUSTED_OVERLAY              = 1 << 8,
+
+    /**
+     * Indicates that this window wants to listen for when there is a touch DOWN event
+     * that occurs outside its touchable bounds. When such an event occurs, this window
+     * will receive a MotionEvent with ACTION_OUTSIDE.
+     */
+    WATCH_OUTSIDE_TOUCH          = 1 << 9,
+
+    /**
+     * When set, this flag allows touches to leave the current window whenever the finger
+     * moves above another window. When this happens, the window that touch has just left
+     * (the current window) will receive ACTION_CANCEL, and the window that touch has entered
+     * will receive ACTION_DOWN, and the remainder of the touch gesture will only go to the
+     * new window. Without this flag, the entire gesture is sent to the current window, even
+     * if the touch leaves the window's bounds.
+     */
+    SLIPPERY                     = 1 << 10,
+
+    /**
+     * When this window has focus, does not call user activity for all input events so
+     * the application will have to do it itself.
+     */
+    DISABLE_USER_ACTIVITY        = 1 << 11,
+
+    /**
+     * Internal flag used to indicate that input should be dropped on this window.
+     */
+    DROP_INPUT                   = 1 << 12,
+
+    /**
+     * Internal flag used to indicate that input should be dropped on this window if this window
+     * is obscured.
+     */
+    DROP_INPUT_IF_OBSCURED       = 1 << 13,
+
+    /**
+     * An input spy window. This window will receive all pointer events within its touchable
+     * area, but will not stop events from being sent to other windows below it in z-order.
+     * An input event will be dispatched to all spy windows above the top non-spy window at the
+     * event's coordinates.
+     */
+    SPY                          = 1 << 14,
+
+    /**
+     * When used with {@link #NOT_TOUCHABLE}, this window will continue to receive events from
+     * a stylus device within its touchable region. All other pointer events, such as from a
+     * mouse or touchscreen, will be dispatched to the windows behind it.
+     *
+     * This configuration has no effect when the config {@link #NOT_TOUCHABLE} is not set.
+     *
+     * It is not valid to set this configuration if {@link #TRUSTED_OVERLAY} is not set.
+     */
+    INTERCEPTS_STYLUS            = 1 << 15,
+}
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 07c5dd8..84e84dd 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -122,6 +122,9 @@
         ":librenderengine_threaded_sources",
         ":librenderengine_skia_sources",
     ],
+    header_libs: [
+        "libtonemap_headers",
+    ],
     include_dirs: [
         "external/skia/src/gpu",
     ],
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index a426850..d91af1e 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -42,6 +42,9 @@
         "libshaders",
         "libtonemap",
     ],
+    header_libs: [
+        "libtonemap_headers",
+    ],
 
     shared_libs: [
         "libbase",
diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp
index 390b228..1cd143e 100644
--- a/libs/shaders/Android.bp
+++ b/libs/shaders/Android.bp
@@ -38,6 +38,10 @@
         "libui-types",
     ],
 
+    header_libs: [
+        "libtonemap_headers",
+    ],
+
     srcs: [
         "shaders.cpp",
     ],
diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp
index 5360fe2..99d1b22 100644
--- a/libs/tonemap/Android.bp
+++ b/libs/tonemap/Android.bp
@@ -25,7 +25,6 @@
     name: "libtonemap",
     vendor_available: true,
 
-    export_include_dirs: ["include"],
     local_include_dirs: ["include"],
 
     shared_libs: [
@@ -41,3 +40,9 @@
         "tonemap.cpp",
     ],
 }
+
+cc_library_headers {
+    name: "libtonemap_headers",
+    vendor_available: true,
+    export_include_dirs: ["include"],
+}
diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp
index f46f3fa..d519482 100644
--- a/libs/tonemap/tests/Android.bp
+++ b/libs/tonemap/tests/Android.bp
@@ -27,6 +27,9 @@
     srcs: [
         "tonemap_test.cpp",
     ],
+    header_libs: [
+        "libtonemap_headers",
+    ],
     shared_libs: [
         "android.hardware.graphics.common-V3-ndk",
     ],
diff --git a/opengl/OWNERS b/opengl/OWNERS
index a9bd4bb..a47fb9a 100644
--- a/opengl/OWNERS
+++ b/opengl/OWNERS
@@ -1,6 +1,12 @@
+abdolrashidi@google.com
+cclao@google.com
 chrisforbes@google.com
 cnorthrop@google.com
 ianelliott@google.com
 jessehall@google.com
+lfy@google.com
 lpy@google.com
 timvp@google.com
+romanl@google.com
+vantablack@google.com
+yuxinhu@google.com
diff --git a/services/gpuservice/gpuwork/GpuWork.cpp b/services/gpuservice/gpuwork/GpuWork.cpp
index 67ce9f2..974ae38 100644
--- a/services/gpuservice/gpuwork/GpuWork.cpp
+++ b/services/gpuservice/gpuwork/GpuWork.cpp
@@ -39,17 +39,30 @@
 #include <map>
 #include <mutex>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "gpuwork/gpu_work.h"
 
-#define MS_IN_NS (1000000)
+#define ONE_MS_IN_NS (10000000)
 
 namespace android {
 namespace gpuwork {
 
 namespace {
 
+bool lessThanGpuIdUid(const android::gpuwork::GpuIdUid& l, const android::gpuwork::GpuIdUid& r) {
+    return std::tie(l.gpu_id, l.uid) < std::tie(r.gpu_id, r.uid);
+}
+
+size_t hashGpuIdUid(const android::gpuwork::GpuIdUid& gpuIdUid) {
+    return static_cast<size_t>((gpuIdUid.gpu_id << 5U) + gpuIdUid.uid);
+}
+
+bool equalGpuIdUid(const android::gpuwork::GpuIdUid& l, const android::gpuwork::GpuIdUid& r) {
+    return std::tie(l.gpu_id, l.uid) == std::tie(r.gpu_id, r.uid);
+}
+
 // Gets a BPF map from |mapPath|.
 template <class Key, class Value>
 bool getBpfMap(const char* mapPath, bpf::BpfMap<Key, Value>* out) {
@@ -76,24 +89,6 @@
     return result;
 }
 
-template <>
-inline int32_t cast_int32<uint64_t>(uint64_t source) {
-    if (source > std::numeric_limits<int32_t>::max()) {
-        return std::numeric_limits<int32_t>::max();
-    }
-    return static_cast<int32_t>(source);
-}
-
-template <>
-inline int32_t cast_int32<long long>(long long source) {
-    if (source > std::numeric_limits<int32_t>::max()) {
-        return std::numeric_limits<int32_t>::max();
-    } else if (source < std::numeric_limits<int32_t>::min()) {
-        return std::numeric_limits<int32_t>::min();
-    }
-    return static_cast<int32_t>(source);
-}
-
 } // namespace
 
 using base::StringAppendF;
@@ -115,7 +110,7 @@
     {
         std::scoped_lock<std::mutex> lock(mMutex);
         if (mStatsdRegistered) {
-            AStatsManager_clearPullAtomCallback(android::util::GPU_FREQ_TIME_IN_STATE_PER_UID);
+            AStatsManager_clearPullAtomCallback(android::util::GPU_WORK_PER_UID);
         }
     }
 
@@ -144,7 +139,7 @@
         mPreviousMapClearTimePoint = std::chrono::steady_clock::now();
     }
 
-    // Attach the tracepoint ONLY if we got the map above.
+    // Attach the tracepoint.
     if (!attachTracepoint("/sys/fs/bpf/prog_gpu_work_tracepoint_power_gpu_work_period", "power",
                           "gpu_work_period")) {
         return;
@@ -157,8 +152,8 @@
 
     {
         std::lock_guard<std::mutex> lock(mMutex);
-        AStatsManager_setPullAtomCallback(int32_t{android::util::GPU_FREQ_TIME_IN_STATE_PER_UID},
-                                          nullptr, GpuWork::pullAtomCallback, this);
+        AStatsManager_setPullAtomCallback(int32_t{android::util::GPU_WORK_PER_UID}, nullptr,
+                                          GpuWork::pullAtomCallback, this);
         mStatsdRegistered = true;
     }
 
@@ -169,18 +164,18 @@
 
 void GpuWork::dump(const Vector<String16>& /* args */, std::string* result) {
     if (!mInitialized.load()) {
-        result->append("GPU time in state information is not available.\n");
+        result->append("GPU work information is not available.\n");
         return;
     }
 
-    // Ordered map ensures output data is sorted by UID.
-    std::map<Uid, UidTrackingInfo> dumpMap;
+    // Ordered map ensures output data is sorted.
+    std::map<GpuIdUid, UidTrackingInfo, decltype(lessThanGpuIdUid)*> dumpMap(&lessThanGpuIdUid);
 
     {
         std::lock_guard<std::mutex> lock(mMutex);
 
         if (!mGpuWorkMap.isValid()) {
-            result->append("GPU time in state map is not available.\n");
+            result->append("GPU work map is not available.\n");
             return;
         }
 
@@ -189,56 +184,42 @@
         // threads. The buckets are all preallocated. Our eBPF program only updates
         // entries (in-place) or adds entries. |GpuWork| only iterates or clears the
         // map while holding |mMutex|. Given this, we should be able to iterate over
-        // all elements reliably. In the worst case, we might see elements more than
-        // once.
+        // all elements reliably. Nevertheless, we copy into a map to avoid
+        // duplicates.
 
         // Note that userspace reads of BPF maps make a copy of the value, and
         // thus the returned value is not being concurrently accessed by the BPF
         // program (no atomic reads needed below).
 
-        mGpuWorkMap.iterateWithValue([&dumpMap](const Uid& key, const UidTrackingInfo& value,
-                                                const android::bpf::BpfMap<Uid, UidTrackingInfo>&)
-                                             -> base::Result<void> {
-            dumpMap[key] = value;
-            return {};
-        });
+        mGpuWorkMap.iterateWithValue(
+                [&dumpMap](const GpuIdUid& key, const UidTrackingInfo& value,
+                           const android::bpf::BpfMap<GpuIdUid, UidTrackingInfo>&)
+                        -> base::Result<void> {
+                    dumpMap[key] = value;
+                    return {};
+                });
     }
 
-    // Find the largest frequency where some UID has spent time in that frequency.
-    size_t largestFrequencyWithTime = 0;
-    for (const auto& uidToUidInfo : dumpMap) {
-        for (size_t i = largestFrequencyWithTime + 1; i < kNumTrackedFrequencies; ++i) {
-            if (uidToUidInfo.second.frequency_times_ns[i] > 0) {
-                largestFrequencyWithTime = i;
-            }
-        }
-    }
-
-    // Dump time in state information.
+    // Dump work information.
     // E.g.
-    // uid/freq: 0MHz 50MHz 100MHz ...
-    // 1000: 0 0 0 0 ...
-    // 1003: 0 0 3456 0 ...
-    // [errors:3]1006: 0 0 3456 0 ...
+    // GPU work information.
+    // gpu_id uid total_active_duration_ns total_inactive_duration_ns
+    // 0 1000 0 0
+    // 0 1003 1234 123
+    // [errors:3]0 1006 4567 456
 
     // Header.
-    result->append("GPU time in frequency state in ms.\n");
-    result->append("uid/freq: 0MHz");
-    for (size_t i = 1; i <= largestFrequencyWithTime; ++i) {
-        StringAppendF(result, " %zuMHz", i * 50);
-    }
-    result->append("\n");
+    result->append("GPU work information.\ngpu_id uid total_active_duration_ns "
+                   "total_inactive_duration_ns\n");
 
-    for (const auto& uidToUidInfo : dumpMap) {
-        if (uidToUidInfo.second.error_count) {
-            StringAppendF(result, "[errors:%" PRIu32 "]", uidToUidInfo.second.error_count);
+    for (const auto& idToUidInfo : dumpMap) {
+        if (idToUidInfo.second.error_count) {
+            StringAppendF(result, "[errors:%" PRIu32 "]", idToUidInfo.second.error_count);
         }
-        StringAppendF(result, "%" PRIu32 ":", uidToUidInfo.first);
-        for (size_t i = 0; i <= largestFrequencyWithTime; ++i) {
-            StringAppendF(result, " %" PRIu64,
-                          uidToUidInfo.second.frequency_times_ns[i] / MS_IN_NS);
-        }
-        result->append("\n");
+        StringAppendF(result, "%" PRIu32 " %" PRIu32 " %" PRIu64 " %" PRIu64 "\n",
+                      idToUidInfo.first.gpu_id, idToUidInfo.first.uid,
+                      idToUidInfo.second.total_active_duration_ns,
+                      idToUidInfo.second.total_inactive_duration_ns);
     }
 }
 
@@ -275,14 +256,14 @@
     ATRACE_CALL();
 
     GpuWork* gpuWork = reinterpret_cast<GpuWork*>(cookie);
-    if (atomTag == android::util::GPU_FREQ_TIME_IN_STATE_PER_UID) {
-        return gpuWork->pullFrequencyAtoms(data);
+    if (atomTag == android::util::GPU_WORK_PER_UID) {
+        return gpuWork->pullWorkAtoms(data);
     }
 
     return AStatsManager_PULL_SKIP;
 }
 
-AStatsManager_PullAtomCallbackReturn GpuWork::pullFrequencyAtoms(AStatsEventList* data) {
+AStatsManager_PullAtomCallbackReturn GpuWork::pullWorkAtoms(AStatsEventList* data) {
     ATRACE_CALL();
 
     if (!data || !mInitialized.load()) {
@@ -295,96 +276,153 @@
         return AStatsManager_PULL_SKIP;
     }
 
-    std::unordered_map<Uid, UidTrackingInfo> uidInfos;
+    std::unordered_map<GpuIdUid, UidTrackingInfo, decltype(hashGpuIdUid)*, decltype(equalGpuIdUid)*>
+            workMap(32, &hashGpuIdUid, &equalGpuIdUid);
 
     // Iteration of BPF hash maps can be unreliable (no data races, but elements
     // may be repeated), as the map is typically being modified by other
     // threads. The buckets are all preallocated. Our eBPF program only updates
     // entries (in-place) or adds entries. |GpuWork| only iterates or clears the
     // map while holding |mMutex|. Given this, we should be able to iterate over
-    // all elements reliably. In the worst case, we might see elements more than
-    // once.
+    // all elements reliably. Nevertheless, we copy into a map to avoid
+    // duplicates.
 
     // Note that userspace reads of BPF maps make a copy of the value, and thus
     // the returned value is not being concurrently accessed by the BPF program
     // (no atomic reads needed below).
 
-    mGpuWorkMap.iterateWithValue(
-            [&uidInfos](const Uid& key, const UidTrackingInfo& value,
-                        const android::bpf::BpfMap<Uid, UidTrackingInfo>&) -> base::Result<void> {
-                uidInfos[key] = value;
-                return {};
-            });
-
-    ALOGI("pullFrequencyAtoms: uidInfos.size() == %zu", uidInfos.size());
+    mGpuWorkMap.iterateWithValue([&workMap](const GpuIdUid& key, const UidTrackingInfo& value,
+                                            const android::bpf::BpfMap<GpuIdUid, UidTrackingInfo>&)
+                                         -> base::Result<void> {
+        workMap[key] = value;
+        return {};
+    });
 
     // Get a list of just the UIDs; the order does not matter.
     std::vector<Uid> uids;
-    for (const auto& pair : uidInfos) {
-        uids.push_back(pair.first);
+    // Get a list of the GPU IDs, in order.
+    std::set<uint32_t> gpuIds;
+    {
+        // To avoid adding duplicate UIDs.
+        std::unordered_set<Uid> addedUids;
+
+        for (const auto& workInfo : workMap) {
+            if (addedUids.insert(workInfo.first.uid).second) {
+                // Insertion was successful.
+                uids.push_back(workInfo.first.uid);
+            }
+            gpuIds.insert(workInfo.first.gpu_id);
+        }
     }
 
+    ALOGI("pullWorkAtoms: uids.size() == %zu", uids.size());
+    ALOGI("pullWorkAtoms: gpuIds.size() == %zu", gpuIds.size());
+
+    if (gpuIds.size() > kNumGpusHardLimit) {
+        // If we observe a very high number of GPUs then something has probably
+        // gone wrong, so don't log any atoms.
+        return AStatsManager_PULL_SKIP;
+    }
+
+    size_t numSampledUids = kNumSampledUids;
+
+    if (gpuIds.size() > kNumGpusSoftLimit) {
+        // If we observe a high number of GPUs then we just sample 1 UID.
+        numSampledUids = 1;
+    }
+
+    // Remove all UIDs that do not have at least |kMinGpuTimeNanoseconds| on at
+    // least one GPU.
+    {
+        auto uidIt = uids.begin();
+        while (uidIt != uids.end()) {
+            bool hasEnoughGpuTime = false;
+            for (uint32_t gpuId : gpuIds) {
+                auto infoIt = workMap.find(GpuIdUid{gpuId, *uidIt});
+                if (infoIt == workMap.end()) {
+                    continue;
+                }
+                if (infoIt->second.total_active_duration_ns +
+                            infoIt->second.total_inactive_duration_ns >=
+                    kMinGpuTimeNanoseconds) {
+                    hasEnoughGpuTime = true;
+                    break;
+                }
+            }
+            if (hasEnoughGpuTime) {
+                ++uidIt;
+            } else {
+                uidIt = uids.erase(uidIt);
+            }
+        }
+    }
+
+    ALOGI("pullWorkAtoms: after removing uids with very low GPU time: uids.size() == %zu",
+          uids.size());
+
     std::random_device device;
     std::default_random_engine random_engine(device());
 
-    // If we have more than |kNumSampledUids| UIDs, choose |kNumSampledUids|
+    // If we have more than |numSampledUids| UIDs, choose |numSampledUids|
     // random UIDs. We swap them to the front of the list. Given the list
     // indices 0..i..n-1, we have the following inclusive-inclusive ranges:
     // - [0, i-1] == the randomly chosen elements.
     // - [i, n-1] == the remaining unchosen elements.
-    if (uids.size() > kNumSampledUids) {
-        for (size_t i = 0; i < kNumSampledUids; ++i) {
+    if (uids.size() > numSampledUids) {
+        for (size_t i = 0; i < numSampledUids; ++i) {
             std::uniform_int_distribution<size_t> uniform_dist(i, uids.size() - 1);
             size_t random_index = uniform_dist(random_engine);
             std::swap(uids[i], uids[random_index]);
         }
-        // Only keep the front |kNumSampledUids| elements.
-        uids.resize(kNumSampledUids);
+        // Only keep the front |numSampledUids| elements.
+        uids.resize(numSampledUids);
     }
 
-    ALOGI("pullFrequencyAtoms: uids.size() == %zu", uids.size());
+    ALOGI("pullWorkAtoms: after random selection: uids.size() == %zu", uids.size());
 
     auto now = std::chrono::steady_clock::now();
-
-    int32_t duration = cast_int32(
+    long long duration =
             std::chrono::duration_cast<std::chrono::seconds>(now - mPreviousMapClearTimePoint)
-                    .count());
+                    .count();
+    if (duration > std::numeric_limits<int32_t>::max() || duration < 0) {
+        // This is essentially impossible. If it does somehow happen, give up,
+        // but still clear the map.
+        clearMap();
+        return AStatsManager_PULL_SKIP;
+    }
 
-    for (const Uid uid : uids) {
-        const UidTrackingInfo& info = uidInfos[uid];
-        ALOGI("pullFrequencyAtoms: adding stats for UID %" PRIu32, uid);
-        android::util::addAStatsEvent(data, int32_t{android::util::GPU_FREQ_TIME_IN_STATE_PER_UID},
-                                      // uid
-                                      bitcast_int32(uid),
-                                      // time_duration_seconds
-                                      int32_t{duration},
-                                      // max_freq_mhz
-                                      int32_t{1000},
-                                      // freq_0_mhz_time_millis
-                                      cast_int32(info.frequency_times_ns[0] / 1000000),
-                                      // freq_50_mhz_time_millis
-                                      cast_int32(info.frequency_times_ns[1] / 1000000),
-                                      // ... etc. ...
-                                      cast_int32(info.frequency_times_ns[2] / 1000000),
-                                      cast_int32(info.frequency_times_ns[3] / 1000000),
-                                      cast_int32(info.frequency_times_ns[4] / 1000000),
-                                      cast_int32(info.frequency_times_ns[5] / 1000000),
-                                      cast_int32(info.frequency_times_ns[6] / 1000000),
-                                      cast_int32(info.frequency_times_ns[7] / 1000000),
-                                      cast_int32(info.frequency_times_ns[8] / 1000000),
-                                      cast_int32(info.frequency_times_ns[9] / 1000000),
-                                      cast_int32(info.frequency_times_ns[10] / 1000000),
-                                      cast_int32(info.frequency_times_ns[11] / 1000000),
-                                      cast_int32(info.frequency_times_ns[12] / 1000000),
-                                      cast_int32(info.frequency_times_ns[13] / 1000000),
-                                      cast_int32(info.frequency_times_ns[14] / 1000000),
-                                      cast_int32(info.frequency_times_ns[15] / 1000000),
-                                      cast_int32(info.frequency_times_ns[16] / 1000000),
-                                      cast_int32(info.frequency_times_ns[17] / 1000000),
-                                      cast_int32(info.frequency_times_ns[18] / 1000000),
-                                      cast_int32(info.frequency_times_ns[19] / 1000000),
-                                      // freq_1000_mhz_time_millis
-                                      cast_int32(info.frequency_times_ns[20] / 1000000));
+    // Log an atom for each (gpu id, uid) pair for which we have data.
+    for (uint32_t gpuId : gpuIds) {
+        for (Uid uid : uids) {
+            auto it = workMap.find(GpuIdUid{gpuId, uid});
+            if (it == workMap.end()) {
+                continue;
+            }
+            const UidTrackingInfo& info = it->second;
+
+            uint64_t total_active_duration_ms = info.total_active_duration_ns / ONE_MS_IN_NS;
+            uint64_t total_inactive_duration_ms = info.total_inactive_duration_ns / ONE_MS_IN_NS;
+
+            // Skip this atom if any numbers are out of range. |duration| is
+            // already checked above.
+            if (total_active_duration_ms > std::numeric_limits<int32_t>::max() ||
+                total_inactive_duration_ms > std::numeric_limits<int32_t>::max()) {
+                continue;
+            }
+
+            ALOGI("pullWorkAtoms: adding stats for GPU ID %" PRIu32 "; UID %" PRIu32, gpuId, uid);
+            android::util::addAStatsEvent(data, int32_t{android::util::GPU_WORK_PER_UID},
+                                          // uid
+                                          bitcast_int32(uid),
+                                          // gpu_id
+                                          bitcast_int32(gpuId),
+                                          // time_duration_seconds
+                                          static_cast<int32_t>(duration),
+                                          // total_active_duration_millis
+                                          static_cast<int32_t>(total_active_duration_ms),
+                                          // total_inactive_duration_millis
+                                          static_cast<int32_t>(total_inactive_duration_ms));
+        }
     }
     clearMap();
     return AStatsManager_PULL_SUCCESS;
@@ -435,7 +473,7 @@
     uint64_t numEntries = globalData.value().num_map_entries;
 
     // If the map is <=75% full, we do nothing.
-    if (numEntries <= (kMaxTrackedUids / 4) * 3) {
+    if (numEntries <= (kMaxTrackedGpuIdUids / 4) * 3) {
         return;
     }
 
@@ -456,22 +494,22 @@
 
     // Iterating BPF maps to delete keys is tricky. If we just repeatedly call
     // |getFirstKey()| and delete that, we may loop forever (or for a long time)
-    // because our BPF program might be repeatedly re-adding UID keys. Also,
-    // even if we limit the number of elements we try to delete, we might only
-    // delete new entries, leaving old entries in the map. If we delete a key A
-    // and then call |getNextKey(A)|, the first key in the map is returned, so
-    // we have the same issue.
+    // because our BPF program might be repeatedly re-adding keys. Also, even if
+    // we limit the number of elements we try to delete, we might only delete
+    // new entries, leaving old entries in the map. If we delete a key A and
+    // then call |getNextKey(A)|, the first key in the map is returned, so we
+    // have the same issue.
     //
     // Thus, we instead get the next key and then delete the previous key. We
     // also limit the number of deletions we try, just in case.
 
-    base::Result<Uid> key = mGpuWorkMap.getFirstKey();
+    base::Result<GpuIdUid> key = mGpuWorkMap.getFirstKey();
 
-    for (size_t i = 0; i < kMaxTrackedUids; ++i) {
+    for (size_t i = 0; i < kMaxTrackedGpuIdUids; ++i) {
         if (!key.ok()) {
             break;
         }
-        base::Result<Uid> previousKey = key;
+        base::Result<GpuIdUid> previousKey = key;
         key = mGpuWorkMap.getNextKey(previousKey.value());
         mGpuWorkMap.deleteValue(previousKey.value());
     }
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
index a0e1b22..c8e79a2 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
@@ -27,66 +27,124 @@
 #endif
 
 #define S_IN_NS (1000000000)
-#define MHZ_IN_KHZS (1000)
+#define SMALL_TIME_GAP_LIMIT_NS (S_IN_NS)
 
-typedef uint32_t Uid;
-
-// A map from UID to |UidTrackingInfo|.
-DEFINE_BPF_MAP_GRW(gpu_work_map, HASH, Uid, UidTrackingInfo, kMaxTrackedUids, AID_GRAPHICS);
+// A map from GpuIdUid (GPU ID and application UID) to |UidTrackingInfo|.
+DEFINE_BPF_MAP_GRW(gpu_work_map, HASH, GpuIdUid, UidTrackingInfo, kMaxTrackedGpuIdUids,
+                   AID_GRAPHICS);
 
 // A map containing a single entry of |GlobalData|.
 DEFINE_BPF_MAP_GRW(gpu_work_global_data, ARRAY, uint32_t, GlobalData, 1, AID_GRAPHICS);
 
-// GpuUidWorkPeriodEvent defines the structure of a kernel tracepoint under the
-// tracepoint system (also referred to as the group) "power" and name
-// "gpu_work_period". In summary, the kernel tracepoint should be
-// "power/gpu_work_period", available at:
+// Defines the structure of the kernel tracepoint:
 //
 //  /sys/kernel/tracing/events/power/gpu_work_period/
 //
-// GpuUidWorkPeriodEvent defines a non-overlapping, non-zero period of time when
-// work was running on the GPU for a given application (identified by its UID;
-// the persistent, unique ID of the application) from |start_time_ns| to
-// |end_time_ns|. Drivers should issue this tracepoint as soon as possible
-// (within 1 second) after |end_time_ns|. For a given UID, periods must not
-// overlap, but periods from different UIDs can overlap (and should overlap, if
-// and only if that is how the work was executed). The period includes
-// information such as |frequency_khz|, the frequency that the GPU was running
-// at during the period, and |includes_compute_work|, whether the work included
-// compute shader work (not just graphics work). GPUs may have multiple
-// frequencies that can be adjusted, but the driver should report the frequency
-// that most closely estimates power usage (e.g. the frequency of shader cores,
-// not a scheduling unit).
+// Drivers must define an appropriate gpu_work_period kernel tracepoint (for
+// example, using the DECLARE_EVENT_CLASS and DEFINE_EVENT macros) such that the
+// arguments/fields match the fields of |GpuWorkPeriodEvent|, excluding the
+// initial "common" field. Drivers must invoke the tracepoint (also referred to
+// as emitting the event) as described below. Note that the description below
+// assumes a single physical GPU and its driver; for devices with multiple GPUs,
+// each GPU and its driver should emit events independently, using a different
+// value for |gpu_id| per GPU.
 //
-// If any information changes while work from the UID is running on the GPU
-// (e.g. the GPU frequency changes, or the work starts/stops including compute
-// work) then the driver must conceptually end the period, issue the tracepoint,
-// start tracking a new period, and eventually issue a second tracepoint when
-// the work completes or when the information changes again. In this case, the
-// |end_time_ns| of the first period must equal the |start_time_ns| of the
-// second period. The driver may also end and start a new period (without a
-// gap), even if no information changes. For example, this might be convenient
-// if there is a collection of work from a UID running on the GPU for a long
-// time; ending and starting a period as individual parts of the work complete
-// allows the consumer of the tracepoint to be updated about the ongoing work.
+// |GpuWorkPeriodEvent| defines a non-overlapping, non-zero period of time from
+// |start_time_ns| (inclusive) until |end_time_ns| (exclusive) for a given
+// |uid|, and includes details of how much work the GPU was performing for |uid|
+// during the period. When GPU work for a given |uid| runs on the GPU, the
+// driver must track one or more periods that cover the time where the work was
+// running, and emit events soon after. The driver should try to emit the event
+// for a period at most 1 second after |end_time_ns|, and must emit the event at
+// most 2 seconds after |end_time_ns|. A period's duration (|end_time_ns| -
+// |start_time_ns|) must be at most 1 second. Periods for different |uids| can
+// overlap, but periods for the same |uid| must not overlap. The driver must
+// emit events for the same |uid| in strictly increasing order of
+// |start_time_ns|, such that it is guaranteed that the tracepoint call for a
+// period for |uid| has returned before the tracepoint call for the next period
+// for |uid| is made. Note that synchronization may be necessary if the driver
+// emits events for the same |uid| from different threads/contexts. Note that
+// |end_time_ns| for a period for a |uid| may equal the |start_time_ns| of the
+// next period for |uid|. The driver should try to avoid emitting a large number
+// of events in a short time period (e.g. 1000 events per second) for a given
+// |uid|.
 //
-// For a given UID, the tracepoints must be emitted in order; that is, the
-// |start_time_ns| of each subsequent tracepoint for a given UID must be
-// monotonically increasing.
+// The |total_active_duration_ns| must be set to the approximate total amount of
+// time the GPU spent running work for |uid| within the period, without
+// "double-counting" parallel GPU work on the same GPU for the same |uid|. "GPU
+// work" should correspond to the "GPU slices" shown in the AGI (Android GPU
+// Inspector) tool, and so should include work such as fragment and non-fragment
+// work/shaders running on the shader cores of the GPU. For example, given the
+// following:
+//  - A period has:
+//    - |start_time_ns|: 100,000,000 ns
+//    - |end_time_ns|:   800,000,000 ns
+//  - Some GPU vertex work (A):
+//    - started at:      200,000,000 ns
+//    - ended at:        400,000,000 ns
+//  - Some GPU fragment work (B):
+//    - started at:      300,000,000 ns
+//    - ended at:        500,000,000 ns
+//  - Some GPU fragment work (C):
+//    - started at:      300,000,000 ns
+//    - ended at:        400,000,000 ns
+//  - Some GPU fragment work (D):
+//    - started at:      600,000,000 ns
+//    - ended at:        700,000,000 ns
+//
+// The |total_active_duration_ns| would be 400,000,000 ns, because GPU work for
+// |uid| was executing:
+//  - from 200,000,000 ns to 500,000,000 ns, giving a duration of 300,000,000 ns
+//    (encompassing GPU work A, B, and C)
+//  - from 600,000,000 ns to 700,000,000 ns, giving a duration of 100,000,000 ns
+//    (GPU work D)
+//
+// Thus, the |total_active_duration_ns| is the sum of the (non-overlapping)
+// durations. Drivers may not have efficient access to the exact start and end
+// times of all GPU work, as shown above, but drivers should try to
+// approximate/aggregate the value of |total_active_duration_ns| as accurately
+// as possible within the limitations of the hardware, without double-counting
+// parallel GPU work for the same |uid|. The |total_active_duration_ns| value
+// must be less than or equal to the period duration (|end_time_ns| -
+// |start_time_ns|); if the aggregation approach might violate this requirement
+// then the driver must clamp |total_active_duration_ns| to be at most the
+// period duration.
+//
+// Note that the above description allows for a certain amount of flexibility in
+// how the driver tracks periods and emits the events. We list a few examples of
+// how drivers might implement the above:
+//
+// - 1: The driver could track periods for all |uid| values at fixed intervals
+//   of 1 second. Thus, every period duration would be exactly 1 second, and
+//   periods from different |uid|s that overlap would have the same
+//   |start_time_ns| and |end_time_ns| values.
+//
+// - 2: The driver could track periods with many different durations (up to 1
+//   second), as needed in order to cover the GPU work for each |uid|.
+//   Overlapping periods for different |uid|s may have very different durations,
+//   as well as different |start_time_ns| and |end_time_ns| values.
+//
+// - 3: The driver could track fine-grained periods with different durations
+//   that precisely cover the time where GPU work is running for each |uid|.
+//   Thus, |total_active_duration_ns| would always equal the period duration.
+//   For example, if a game was running at 60 frames per second, the driver
+//   would most likely emit _at least_ 60 events per second (probably more, as
+//   there would likely be multiple "chunks" of GPU work per frame, with gaps
+//   between each chunk). However, the driver may sometimes need to resort to
+//   more coarse-grained periods to avoid emitting thousands of events per
+//   second for a |uid|, where |total_active_duration_ns| would then be less
+//   than the period duration.
 typedef struct {
     // Actual fields start at offset 8.
-    uint64_t reserved;
+    uint64_t common;
 
-    // The UID of the process (i.e. persistent, unique ID of the Android app)
-    // that submitted work to the GPU.
+    // A value that uniquely identifies the GPU within the system.
+    uint32_t gpu_id;
+
+    // The UID of the application (i.e. persistent, unique ID of the Android
+    // app) that submitted work to the GPU.
     uint32_t uid;
 
-    // The GPU frequency during the period. GPUs may have multiple frequencies
-    // that can be adjusted, but the driver should report the frequency that
-    // would most closely estimate power usage (e.g. the frequency of shader
-    // cores, not a scheduling unit).
-    uint32_t frequency_khz;
-
     // The start time of the period in nanoseconds. The clock must be
     // CLOCK_MONOTONIC, as returned by the ktime_get_ns(void) function.
     uint64_t start_time_ns;
@@ -95,54 +153,44 @@
     // CLOCK_MONOTONIC, as returned by the ktime_get_ns(void) function.
     uint64_t end_time_ns;
 
-    // Flags about the work. Reserved for future use.
-    uint64_t flags;
+    // The amount of time the GPU was running GPU work for |uid| during the
+    // period, in nanoseconds, without double-counting parallel GPU work for the
+    // same |uid|. For example, this might include the amount of time the GPU
+    // spent performing shader work (vertex work, fragment work, etc.) for
+    // |uid|.
+    uint64_t total_active_duration_ns;
 
-    // The maximum GPU frequency allowed during the period according to the
-    // thermal throttling policy. Must be 0 if no thermal throttling was
-    // enforced during the period. The value must relate to |frequency_khz|; in
-    // other words, if |frequency_khz| is the frequency of the GPU shader cores,
-    // then |thermally_throttled_max_frequency_khz| must be the maximum allowed
-    // frequency of the GPU shader cores (not the maximum allowed frequency of
-    // some other part of the GPU).
-    //
-    // Note: Unlike with other fields of this struct, if the
-    // |thermally_throttled_max_frequency_khz| value conceptually changes while
-    // work from a UID is running on the GPU then the GPU driver does NOT need
-    // to accurately report this by ending the period and starting to track a
-    // new period; instead, the GPU driver may report any one of the
-    // |thermally_throttled_max_frequency_khz| values that was enforced during
-    // the period. The motivation for this relaxation is that we assume the
-    // maximum frequency will not be changing rapidly, and so capturing the
-    // exact point at which the change occurs is unnecessary.
-    uint32_t thermally_throttled_max_frequency_khz;
+} GpuWorkPeriodEvent;
 
-} GpuUidWorkPeriodEvent;
-
-_Static_assert(offsetof(GpuUidWorkPeriodEvent, uid) == 8 &&
-                       offsetof(GpuUidWorkPeriodEvent, frequency_khz) == 12 &&
-                       offsetof(GpuUidWorkPeriodEvent, start_time_ns) == 16 &&
-                       offsetof(GpuUidWorkPeriodEvent, end_time_ns) == 24 &&
-                       offsetof(GpuUidWorkPeriodEvent, flags) == 32 &&
-                       offsetof(GpuUidWorkPeriodEvent, thermally_throttled_max_frequency_khz) == 40,
-               "Field offsets of struct GpuUidWorkPeriodEvent must not be changed because they "
+_Static_assert(offsetof(GpuWorkPeriodEvent, gpu_id) == 8 &&
+                       offsetof(GpuWorkPeriodEvent, uid) == 12 &&
+                       offsetof(GpuWorkPeriodEvent, start_time_ns) == 16 &&
+                       offsetof(GpuWorkPeriodEvent, end_time_ns) == 24 &&
+                       offsetof(GpuWorkPeriodEvent, total_active_duration_ns) == 32,
+               "Field offsets of struct GpuWorkPeriodEvent must not be changed because they "
                "must match the tracepoint field offsets found via adb shell cat "
                "/sys/kernel/tracing/events/power/gpu_work_period/format");
 
 DEFINE_BPF_PROG("tracepoint/power/gpu_work_period", AID_ROOT, AID_GRAPHICS, tp_gpu_work_period)
-(GpuUidWorkPeriodEvent* const args) {
-    // Note: In BPF programs, |__sync_fetch_and_add| is translated to an atomic
+(GpuWorkPeriodEvent* const period) {
+    // Note: In eBPF programs, |__sync_fetch_and_add| is translated to an atomic
     // add.
 
-    const uint32_t uid = args->uid;
+    // Return 1 to avoid blocking simpleperf from receiving events.
+    const int ALLOW = 1;
 
-    // Get |UidTrackingInfo| for |uid|.
-    UidTrackingInfo* uid_tracking_info = bpf_gpu_work_map_lookup_elem(&uid);
+    GpuIdUid gpu_id_and_uid;
+    __builtin_memset(&gpu_id_and_uid, 0, sizeof(gpu_id_and_uid));
+    gpu_id_and_uid.gpu_id = period->gpu_id;
+    gpu_id_and_uid.uid = period->uid;
+
+    // Get |UidTrackingInfo|.
+    UidTrackingInfo* uid_tracking_info = bpf_gpu_work_map_lookup_elem(&gpu_id_and_uid);
     if (!uid_tracking_info) {
         // There was no existing entry, so we add a new one.
         UidTrackingInfo initial_info;
         __builtin_memset(&initial_info, 0, sizeof(initial_info));
-        if (0 == bpf_gpu_work_map_update_elem(&uid, &initial_info, BPF_NOEXIST)) {
+        if (0 == bpf_gpu_work_map_update_elem(&gpu_id_and_uid, &initial_info, BPF_NOEXIST)) {
             // We added an entry to the map, so we increment our entry counter in
             // |GlobalData|.
             const uint32_t zero = 0;
@@ -154,66 +202,80 @@
                 __sync_fetch_and_add(&global_data->num_map_entries, 1);
             }
         }
-        uid_tracking_info = bpf_gpu_work_map_lookup_elem(&uid);
+        uid_tracking_info = bpf_gpu_work_map_lookup_elem(&gpu_id_and_uid);
         if (!uid_tracking_info) {
             // This should never happen, unless entries are getting deleted at
             // this moment. If so, we just give up.
-            return 0;
+            return ALLOW;
         }
     }
 
-    // The period duration must be non-zero.
-    if (args->start_time_ns >= args->end_time_ns) {
+    if (
+            // The period duration must be non-zero.
+            period->start_time_ns >= period->end_time_ns ||
+            // The period duration must be at most 1 second.
+            (period->end_time_ns - period->start_time_ns) > S_IN_NS) {
         __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
-        return 0;
+        return ALLOW;
     }
 
-    // The frequency must not be 0.
-    if (args->frequency_khz == 0) {
-        __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
-        return 0;
+    // If |total_active_duration_ns| is 0 then no GPU work occurred and there is
+    // nothing to do.
+    if (period->total_active_duration_ns == 0) {
+        return ALLOW;
     }
 
-    // Calculate the frequency index: see |UidTrackingInfo.frequency_times_ns|.
-    // Round to the nearest 50MHz bucket.
-    uint32_t frequency_index =
-            ((args->frequency_khz / MHZ_IN_KHZS) + (kFrequencyGranularityMhz / 2)) /
-            kFrequencyGranularityMhz;
-    if (frequency_index >= kNumTrackedFrequencies) {
-        frequency_index = kNumTrackedFrequencies - 1;
-    }
+    // Update |uid_tracking_info->total_active_duration_ns|.
+    __sync_fetch_and_add(&uid_tracking_info->total_active_duration_ns,
+                         period->total_active_duration_ns);
 
-    // Never round down to 0MHz, as this is a special bucket (see below) and not
-    // an actual operating point.
-    if (frequency_index == 0) {
-        frequency_index = 1;
-    }
-
-    // Update time in state.
-    __sync_fetch_and_add(&uid_tracking_info->frequency_times_ns[frequency_index],
-                         args->end_time_ns - args->start_time_ns);
-
-    if (uid_tracking_info->previous_end_time_ns > args->start_time_ns) {
-        // This must not happen because per-UID periods must not overlap and
-        // must be emitted in order.
+    // |small_gap_time_ns| is the time gap between the current and previous
+    // active period, which could be 0. If the gap is more than
+    // |SMALL_TIME_GAP_LIMIT_NS| then |small_gap_time_ns| will be set to 0
+    // because we want to estimate the small gaps between "continuous" GPU work.
+    uint64_t small_gap_time_ns = 0;
+    if (uid_tracking_info->previous_active_end_time_ns > period->start_time_ns) {
+        // The current period appears to have occurred before the previous
+        // active period, which must not happen because per-UID periods must not
+        // overlap and must be emitted in strictly increasing order of
+        // |start_time_ns|.
         __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
     } else {
-        // The period appears to have been emitted after the previous, as
-        // expected, so we can calculate the gap between this and the previous
-        // period.
-        const uint64_t gap_time = args->start_time_ns - uid_tracking_info->previous_end_time_ns;
+        // The current period appears to have been emitted after the previous
+        // active period, as expected, so we can calculate the gap between the
+        // current and previous active period.
+        small_gap_time_ns = period->start_time_ns - uid_tracking_info->previous_active_end_time_ns;
 
-        // Update |previous_end_time_ns|.
-        uid_tracking_info->previous_end_time_ns = args->end_time_ns;
+        // Update |previous_active_end_time_ns|.
+        uid_tracking_info->previous_active_end_time_ns = period->end_time_ns;
 
-        // Update the special 0MHz frequency time, which stores the gaps between
-        // periods, but only if the gap is < 1 second.
-        if (gap_time > 0 && gap_time < S_IN_NS) {
-            __sync_fetch_and_add(&uid_tracking_info->frequency_times_ns[0], gap_time);
+        // We want to estimate the small gaps between "continuous" GPU work; if
+        // the gap is more than |SMALL_TIME_GAP_LIMIT_NS| then we don't consider
+        // this "continuous" GPU work.
+        if (small_gap_time_ns > SMALL_TIME_GAP_LIMIT_NS) {
+            small_gap_time_ns = 0;
         }
     }
 
-    return 0;
+    uint64_t period_total_inactive_time_ns = 0;
+    const uint64_t period_duration_ns = period->end_time_ns - period->start_time_ns;
+    // |period->total_active_duration_ns| is the active time within the period duration, so
+    // it must not be larger than |period_duration_ns|.
+    if (period->total_active_duration_ns > period_duration_ns) {
+        __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
+    } else {
+        period_total_inactive_time_ns = period_duration_ns - period->total_active_duration_ns;
+    }
+
+    // Update |uid_tracking_info->total_inactive_duration_ns| by adding the
+    // inactive time from this period, plus the small gap between the current
+    // and previous active period. Either or both of these values could be 0.
+    if (small_gap_time_ns > 0 || period_total_inactive_time_ns > 0) {
+        __sync_fetch_and_add(&uid_tracking_info->total_inactive_duration_ns,
+                             small_gap_time_ns + period_total_inactive_time_ns);
+    }
+
+    return ALLOW;
 }
 
 LICENSE("Apache 2.0");
diff --git a/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h b/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h
index 4fe8464..57338f4 100644
--- a/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h
+++ b/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h
@@ -25,18 +25,28 @@
 namespace gpuwork {
 #endif
 
+typedef struct  {
+    uint32_t gpu_id;
+    uint32_t uid;
+} GpuIdUid;
+
 typedef struct {
-    // The end time of the previous period from this UID in nanoseconds.
-    uint64_t previous_end_time_ns;
+    // The end time of the previous period where the GPU was active for the UID,
+    // in nanoseconds.
+    uint64_t previous_active_end_time_ns;
 
-    // The time spent at each GPU frequency while running GPU work from the UID,
-    // in nanoseconds. Array index i stores the time for frequency i*50 MHz. So
-    // index 0 is 0Mhz, index 1 is 50MHz, index 2 is 100MHz, etc., up to index
-    // |kNumTrackedFrequencies|.
-    uint64_t frequency_times_ns[21];
+    // The total amount of time the GPU has spent running work for the UID, in
+    // nanoseconds.
+    uint64_t total_active_duration_ns;
 
-    // The number of times we received |GpuUidWorkPeriodEvent| events in an
-    // unexpected order. See |GpuUidWorkPeriodEvent|.
+    // The total amount of time of the "gaps" between "continuous" GPU work for
+    // the UID, in nanoseconds. This is estimated by ignoring large gaps between
+    // GPU work for this UID.
+    uint64_t total_inactive_duration_ns;
+
+    // The number of errors detected due to |GpuWorkPeriodEvent| events for the
+    // UID violating the specification in some way. E.g. periods with a zero or
+    // negative duration.
     uint32_t error_count;
 
 } UidTrackingInfo;
@@ -48,14 +58,10 @@
     uint64_t num_map_entries;
 } GlobalData;
 
-static const uint32_t kMaxTrackedUids = 512;
-static const uint32_t kFrequencyGranularityMhz = 50;
-static const uint32_t kNumTrackedFrequencies = 21;
+// The maximum number of tracked GPU ID and UID pairs (|GpuIdUid|).
+static const uint32_t kMaxTrackedGpuIdUids = 512;
 
 #ifdef __cplusplus
-static_assert(kNumTrackedFrequencies ==
-              std::extent<decltype(UidTrackingInfo::frequency_times_ns)>::value);
-
 } // namespace gpuwork
 } // namespace android
 #endif
diff --git a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
index b6f493d..acecd56 100644
--- a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
+++ b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
@@ -41,7 +41,7 @@
 
     void initialize();
 
-    // Dumps the GPU time in frequency state information.
+    // Dumps the GPU work information.
     void dump(const Vector<String16>& args, std::string* result);
 
 private:
@@ -55,7 +55,7 @@
                                                                  AStatsEventList* data,
                                                                  void* cookie);
 
-    AStatsManager_PullAtomCallbackReturn pullFrequencyAtoms(AStatsEventList* data);
+    AStatsManager_PullAtomCallbackReturn pullWorkAtoms(AStatsEventList* data);
 
     // Periodically calls |clearMapIfNeeded| to clear the |mGpuWorkMap| map, if
     // needed.
@@ -88,7 +88,7 @@
     std::mutex mMutex;
 
     // BPF map for per-UID GPU work.
-    bpf::BpfMap<Uid, UidTrackingInfo> mGpuWorkMap GUARDED_BY(mMutex);
+    bpf::BpfMap<GpuIdUid, UidTrackingInfo> mGpuWorkMap GUARDED_BY(mMutex);
 
     // BPF map containing a single element for global data.
     bpf::BpfMap<uint32_t, GlobalData> mGpuWorkGlobalDataMap GUARDED_BY(mMutex);
@@ -110,7 +110,18 @@
     bool mStatsdRegistered GUARDED_BY(mMutex) = false;
 
     // The number of randomly chosen (i.e. sampled) UIDs to log stats for.
-    static constexpr int kNumSampledUids = 10;
+    static constexpr size_t kNumSampledUids = 10;
+
+    // A "large" number of GPUs. If we observe more GPUs than this limit then
+    // we reduce the amount of stats we log.
+    static constexpr size_t kNumGpusSoftLimit = 4;
+
+    // A "very large" number of GPUs. If we observe more GPUs than this limit
+    // then we don't log any stats.
+    static constexpr size_t kNumGpusHardLimit = 32;
+
+    // The minimum GPU time needed to actually log stats for a UID.
+    static constexpr uint64_t kMinGpuTimeNanoseconds = 30U * 1000000000U; // 30 seconds.
 
     // The previous time point at which |mGpuWorkMap| was cleared.
     std::chrono::steady_clock::time_point mPreviousMapClearTimePoint GUARDED_BY(mMutex);
diff --git a/services/gpuservice/vts/AndroidTest.xml b/services/gpuservice/vts/AndroidTest.xml
index 02ca07f..34dc2ea 100644
--- a/services/gpuservice/vts/AndroidTest.xml
+++ b/services/gpuservice/vts/AndroidTest.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Runs GpuServiceVendorTests">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="jar" value="GpuServiceVendorTests.jar" />
     </test>
diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
index 4a77d9a..9fa9016 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -78,12 +78,11 @@
                 "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;",
                 "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;",
                 "\tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;",
-                "\tfield:u32 uid;\toffset:8;\tsize:4;\tsigned:0;",
-                "\tfield:u32 frequency;\toffset:12;\tsize:4;\tsigned:0;",
-                "\tfield:u64 start_time;\toffset:16;\tsize:8;\tsigned:0;",
-                "\tfield:u64 end_time;\toffset:24;\tsize:8;\tsigned:0;",
-                "\tfield:u64 flags;\toffset:32;\tsize:8;\tsigned:0;",
-                "\tfield:u32 thermally_throttled_max_frequency_khz;\toffset:40;\tsize:4;\tsigned:0;"
+                "\tfield:u32 gpu_id;\toffset:8;\tsize:4;\tsigned:0;",
+                "\tfield:u32 uid;\toffset:12;\tsize:4;\tsigned:0;",
+                "\tfield:u64 start_time_ns;\toffset:16;\tsize:8;\tsigned:0;",
+                "\tfield:u64 end_time_ns;\toffset:24;\tsize:8;\tsigned:0;",
+                "\tfield:u64 total_active_duration_ns;\toffset:32;\tsize:8;\tsigned:0;"
         );
 
         // We use |fail| rather than |assertEquals| because it allows us to give a clearer message.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 32c3a12..bfdd22c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -66,35 +66,77 @@
 
 namespace {
 
-// Log detailed debug messages about each inbound event notification to the dispatcher.
-constexpr bool DEBUG_INBOUND_EVENT_DETAILS = false;
+/**
+ * Log detailed debug messages about each inbound event notification to the dispatcher.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart)
+ */
+const bool DEBUG_INBOUND_EVENT_DETAILS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
 
-// Log detailed debug messages about each outbound event processed by the dispatcher.
-constexpr bool DEBUG_OUTBOUND_EVENT_DETAILS = false;
+/**
+ * Log detailed debug messages about each outbound event processed by the dispatcher.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart)
+ */
+const bool DEBUG_OUTBOUND_EVENT_DETAILS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO);
 
-// Log debug messages about the dispatch cycle.
-constexpr bool DEBUG_DISPATCH_CYCLE = false;
+/**
+ * Log debug messages about the dispatch cycle.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart)
+ */
+const bool DEBUG_DISPATCH_CYCLE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO);
 
-// Log debug messages about channel creation
-constexpr bool DEBUG_CHANNEL_CREATION = false;
+/**
+ * Log debug messages about channel creation
+ * Enable this via "adb shell setprop log.tag.InputDispatcherChannelCreation DEBUG" (requires
+ * restart)
+ */
+const bool DEBUG_CHANNEL_CREATION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO);
 
-// Log debug messages about input event injection.
-constexpr bool DEBUG_INJECTION = false;
+/**
+ * Log debug messages about input event injection.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart)
+ */
+const bool DEBUG_INJECTION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO);
 
-// Log debug messages about input focus tracking.
-constexpr bool DEBUG_FOCUS = false;
+/**
+ * Log debug messages about input focus tracking.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
+ */
+const bool DEBUG_FOCUS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO);
 
-// Log debug messages about touch mode event
-constexpr bool DEBUG_TOUCH_MODE = false;
+/**
+ * Log debug messages about touch mode event
+ * Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart)
+ */
+const bool DEBUG_TOUCH_MODE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO);
 
-// Log debug messages about touch occlusion
-constexpr bool DEBUG_TOUCH_OCCLUSION = true;
+/**
+ * Log debug messages about touch occlusion
+ * Enable this via "adb shell setprop log.tag.InputDispatcherTouchOcclusion DEBUG" (requires
+ * restart)
+ */
+const bool DEBUG_TOUCH_OCCLUSION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchOcclusion", ANDROID_LOG_INFO);
 
-// Log debug messages about the app switch latency optimization.
-constexpr bool DEBUG_APP_SWITCH = false;
+/**
+ * Log debug messages about the app switch latency optimization.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
+ */
+const bool DEBUG_APP_SWITCH =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO);
 
-// Log debug messages about hover events.
-constexpr bool DEBUG_HOVER = false;
+/**
+ * Log debug messages about hover events.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
+ */
+const bool DEBUG_HOVER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
 
 // Temporarily releases a held mutex for the lifetime of the instance.
 // Named to match std::scoped_lock
@@ -2698,18 +2740,16 @@
 
 std::string InputDispatcher::dumpWindowForTouchOcclusion(const WindowInfo* info,
                                                          bool isTouchedWindow) const {
-    return StringPrintf(INDENT2
-                        "* %spackage=%s/%" PRId32 ", id=%" PRId32 ", mode=%s, alpha=%.2f, "
-                        "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
-                        "], touchableRegion=%s, window={%s}, inputConfig={%s}, inputFeatures={%s}, "
-                        "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
+    return StringPrintf(INDENT2 "* %spackage=%s/%" PRId32 ", id=%" PRId32 ", mode=%s, alpha=%.2f, "
+                                "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
+                                "], touchableRegion=%s, window={%s}, inputConfig={%s}, "
+                                "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
                         isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
                         info->ownerUid, info->id, toString(info->touchOcclusionMode).c_str(),
                         info->alpha, info->frameLeft, info->frameTop, info->frameRight,
                         info->frameBottom, dumpRegion(info->touchableRegion).c_str(),
                         info->name.c_str(), info->inputConfig.string().c_str(),
-                        info->inputFeatures.string().c_str(), toString(info->token != nullptr),
-                        info->applicationInfo.name.c_str(),
+                        toString(info->token != nullptr), info->applicationInfo.name.c_str(),
                         toString(info->applicationInfo.token).c_str());
 }
 
@@ -2787,7 +2827,7 @@
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     if (focusedWindowHandle != nullptr) {
         const WindowInfo* info = focusedWindowHandle->getInfo();
-        if (info->inputFeatures.test(WindowInfo::Feature::DISABLE_USER_ACTIVITY)) {
+        if (info->inputConfig.test(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY)) {
             if (DEBUG_DISPATCH_CYCLE) {
                 ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str());
             }
@@ -4516,7 +4556,7 @@
 bool InputDispatcher::hasResponsiveConnectionLocked(WindowInfoHandle& windowHandle) const {
     sp<Connection> connection = getConnectionLocked(windowHandle.getToken());
     const bool noInputChannel =
-            windowHandle.getInfo()->inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
+            windowHandle.getInfo()->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
     if (connection != nullptr && noInputChannel) {
         ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s",
               windowHandle.getName().c_str(), connection->inputChannel->getName().c_str());
@@ -4566,7 +4606,7 @@
         const WindowInfo* info = handle->getInfo();
         if (getInputChannelLocked(handle->getToken()) == nullptr) {
             const bool noInputChannel =
-                    info->inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
+                    info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
             const bool canReceiveInput =
                     !info->inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) ||
                     !info->inputConfig.test(WindowInfo::InputConfig::NOT_FOCUSABLE);
@@ -4632,7 +4672,7 @@
         const WindowInfo& info = *window->getInfo();
 
         // Ensure all tokens are null if the window has feature NO_INPUT_CHANNEL
-        const bool noInputWindow = info.inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
+        const bool noInputWindow = info.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
         if (noInputWindow && window->getToken() != nullptr) {
             ALOGE("%s has feature NO_INPUT_WINDOW, but a non-null token. Clearing",
                   window->getName().c_str());
@@ -5209,8 +5249,6 @@
                                          windowInfo->applicationInfo.name.c_str(),
                                          toString(windowInfo->applicationInfo.token).c_str());
                     dump += dumpRegion(windowInfo->touchableRegion);
-                    dump += StringPrintf(", inputFeatures=%s",
-                                         windowInfo->inputFeatures.string().c_str());
                     dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
                                          "ms, hasToken=%s, "
                                          "touchOcclusionMode=%s\n",
@@ -6248,13 +6286,14 @@
 
 bool InputDispatcher::shouldDropInput(
         const EventEntry& entry, const sp<android::gui::WindowInfoHandle>& windowHandle) const {
-    if (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT) ||
-        (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED) &&
+    if (windowHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::DROP_INPUT) ||
+        (windowHandle->getInfo()->inputConfig.test(
+                 WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) &&
          isWindowObscuredLocked(windowHandle))) {
-        ALOGW("Dropping %s event targeting %s as requested by input feature %s on display "
-              "%" PRId32 ".",
+        ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on "
+              "display %" PRId32 ".",
               ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
-              windowHandle->getInfo()->inputFeatures.string().c_str(),
+              windowHandle->getInfo()->inputConfig.string().c_str(),
               windowHandle->getInfo()->displayId);
         return true;
     }
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b3f51ee..470d2f6 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -53,6 +53,11 @@
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 static constexpr int32_t SECOND_DISPLAY_ID = 1;
 
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
 // An arbitrary injector pid / uid pair that has permission to inject events.
 static const int32_t INJECTOR_PID = 999;
 static const int32_t INJECTOR_UID = 1001;
@@ -578,11 +583,10 @@
 
     // Rejects pointer down with invalid index.
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
-                     AMOTION_EVENT_ACTION_POINTER_DOWN |
-                             (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                     0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
+                     POINTER_1_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
+                     identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -603,11 +607,10 @@
 
     // Rejects pointer up with invalid index.
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
-                     AMOTION_EVENT_ACTION_POINTER_UP |
-                             (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                     0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
+                     POINTER_1_UP, 0, 0, edgeFlags, metaState, 0, classification, identityTransform,
+                     0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -991,7 +994,7 @@
         mInfo.ownerPid = INJECTOR_PID;
         mInfo.ownerUid = INJECTOR_UID;
         mInfo.displayId = displayId;
-        mInfo.inputConfig = WindowInfo::InputConfig::NONE;
+        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
     }
 
     sp<FakeWindowHandle> clone(
@@ -1035,6 +1038,24 @@
         mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
     }
 
+    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
+
+    void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
+    }
+
+    void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
+
     void setAlpha(float alpha) { mInfo.alpha = alpha; }
 
     void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
@@ -1065,8 +1086,6 @@
         mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
     }
 
-    void setInputFeatures(Flags<WindowInfo::Feature> features) { mInfo.inputFeatures = features; }
-
     void setTrustedOverlay(bool trustedOverlay) {
         mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
     }
@@ -1219,7 +1238,7 @@
 
     void assertNoEvents() {
         if (mInputReceiver == nullptr &&
-            mInfo.inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL)) {
+            mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
             return; // Can't receive events if the window does not have input channel
         }
         ASSERT_NE(nullptr, mInputReceiver)
@@ -1496,6 +1515,10 @@
     return args;
 }
 
+static NotifyMotionArgs generateTouchArgs(int32_t action, const std::vector<PointF>& points) {
+    return generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, points);
+}
+
 static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId) {
     return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
 }
@@ -1731,9 +1754,7 @@
 
     // Second finger down on the top window
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
                                      .x(100)
@@ -1796,9 +1817,7 @@
 
     // Second finger down on the right window
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
                                      .x(100)
@@ -1846,6 +1865,61 @@
     wallpaperWindow->assertNoEvents();
 }
 
+/**
+ * On the display, have a single window, and also an area where there's no window.
+ * First pointer touches the "no window" area of the screen. Second pointer touches the window.
+ * Make sure that the window receives the second pointer, and first pointer is simply ignored.
+ */
+TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Window", DISPLAY_ID);
+
+    mDispatcher->setInputWindows({{DISPLAY_ID, {window}}});
+    NotifyMotionArgs args;
+
+    // Touch down on the empty space
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})));
+
+    mDispatcher->waitForIdle();
+    window->assertNoEvents();
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})));
+    mDispatcher->waitForIdle();
+    window->consumeMotionDown();
+}
+
+/**
+ * Same test as above, but instead of touching the empty space, the first touch goes to
+ * non-touchable window.
+ */
+TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+    window1->setTouchable(false);
+    sp<FakeWindowHandle> window2 =
+            new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
+
+    NotifyMotionArgs args;
+    // Touch down on the non-touchable window
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})));
+
+    mDispatcher->waitForIdle();
+    window1->assertNoEvents();
+    window2->assertNoEvents();
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
+    mDispatcher->waitForIdle();
+    window2->consumeMotionDown();
+}
+
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowLeft =
@@ -2316,9 +2390,7 @@
 
     // Send pointer down to the first window
     NotifyMotionArgs pointerDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {touchPoint, touchPoint});
     mDispatcher->notifyMotion(&pointerDownMotionArgs);
     // Only the first window should get the pointer down event
@@ -2336,9 +2408,7 @@
 
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {touchPoint, touchPoint});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets nothing and the second gets pointer up
@@ -2398,9 +2468,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -2415,9 +2483,7 @@
 
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets nothing and the second gets pointer up
@@ -2466,9 +2532,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -2485,9 +2549,7 @@
     // The rest of the dispatch should proceed as normal
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets MOVE and the second gets pointer up
@@ -2704,9 +2766,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -2715,9 +2775,7 @@
 
     // Send pointer cancel to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
@@ -4173,22 +4231,18 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Release Window 2
-    int32_t actionPointerUp =
-            AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-    touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Update the transform so rotation is set for Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) {
@@ -4200,12 +4254,10 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -4215,15 +4267,13 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 
     // Release Window 2
-    int32_t actionPointerUp =
-            AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-    touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Touch Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -4242,12 +4292,10 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -4301,7 +4349,7 @@
                 new FakeWindowHandle(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
         spy->setTrustedOverlay(true);
         spy->setFocusable(false);
-        spy->setInputFeatures(WindowInfo::Feature::SPY);
+        spy->setSpy(true);
         spy->setDispatchingTimeout(30ms);
         mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, mWindow}}});
         return spy;
@@ -4978,12 +5026,8 @@
                                    ADISPLAY_ID_DEFAULT, 0 /*flags*/);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-
-    motionArgs =
-            generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
+    motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                    {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
     mDispatcher->notifyMotion(&motionArgs);
 
     const std::chrono::duration timeout =
@@ -5094,7 +5138,7 @@
                                               "Window without input channel", ADISPLAY_ID_DEFAULT,
                                               std::make_optional<sp<IBinder>>(nullptr) /*token*/);
 
-        mNoInputWindow->setInputFeatures(WindowInfo::Feature::NO_INPUT_CHANNEL);
+        mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
 
@@ -5136,7 +5180,7 @@
                                           "Window with input channel and NO_INPUT_CHANNEL",
                                           ADISPLAY_ID_DEFAULT);
 
-    mNoInputWindow->setInputFeatures(WindowInfo::Feature::NO_INPUT_CHANNEL);
+    mNoInputWindow->setNoInputChannel(true);
     mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
 
@@ -6051,7 +6095,7 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT);
+    window->setDropInput(true);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
@@ -6070,7 +6114,7 @@
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
-    window->setInputFeatures({});
+    window->setDropInput(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
     keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
@@ -6096,7 +6140,7 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setDropInputIfObscured(true);
     window->setOwnerInfo(222, 222);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
@@ -6116,7 +6160,7 @@
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
-    window->setInputFeatures({});
+    window->setDropInputIfObscured(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
 
     keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
@@ -6142,7 +6186,7 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setDropInputIfObscured(true);
     window->setOwnerInfo(222, 222);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
@@ -6251,7 +6295,7 @@
         name += std::to_string(mSpyCount++);
         sp<FakeWindowHandle> spy =
                 new FakeWindowHandle(application, mDispatcher, name.c_str(), ADISPLAY_ID_DEFAULT);
-        spy->setInputFeatures(WindowInfo::Feature::SPY);
+        spy->setSpy(true);
         spy->setTrustedOverlay(true);
         return spy;
     }
@@ -6509,9 +6553,7 @@
 
     // Second finger down on the window and spy, but the window should not receive the pointer down.
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
@@ -6569,9 +6611,7 @@
     spy->consumeMotionDown();
 
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .pointer(
@@ -6604,9 +6644,7 @@
     spyRight->assertNoEvents();
 
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .pointer(
@@ -6645,9 +6683,7 @@
 
     // Second finger down on window, the window should receive touch down.
     const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
@@ -6699,7 +6735,7 @@
         overlay->setFocusable(false);
         overlay->setOwnerInfo(111, 111);
         overlay->setTouchable(false);
-        overlay->setInputFeatures(WindowInfo::Feature::INTERCEPTS_STYLUS);
+        overlay->setInterceptsStylus(true);
         overlay->setTrustedOverlay(true);
 
         std::shared_ptr<FakeApplicationHandle> application =
@@ -6765,7 +6801,7 @@
 
 TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
     auto [overlay, window] = setupStylusOverlayScenario();
-    overlay->setInputFeatures(overlay->getInfo()->inputFeatures | WindowInfo::Feature::SPY);
+    overlay->setSpy(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
 
     sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 18a6bae..0aca24a 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -311,6 +311,7 @@
             ? 0
             : mBufferInfo.mBufferSlot;
     compositionState->acquireFence = mBufferInfo.mFence;
+    compositionState->frameNumber = mBufferInfo.mFrameNumber;
     compositionState->sidebandStreamHasFrame = false;
 }
 
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 669eaad..8a696f1 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -141,6 +141,8 @@
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     uint64_t mPreviousReleasedFrameNumber = 0;
 
+    uint64_t mPreviousBarrierFrameNumber = 0;
+
     bool mReleasePreviousBuffer = false;
 
     // Stores the last set acquire fence signal time used to populate the callback handle's acquire
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 8bf7f8f..283fe86 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -166,6 +166,7 @@
     int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
     sp<Fence> acquireFence = Fence::NO_FENCE;
     Region surfaceDamage;
+    uint64_t frameNumber = 0;
 
     // The handle to use for a sideband stream for this layer
     sp<NativeHandle> sidebandStream;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 14324de..5fe9476 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -405,6 +405,12 @@
                                                                   : nullptr)};
                     }};
 
+    // Even if the same buffer is passed to BLAST's setBuffer(), we still increment the frame
+    // number and need to treat it as if the buffer changed. Otherwise we break existing
+    // front-buffer rendering paths (such as egl's EGL_SINGLE_BUFFER).
+    OutputLayerState<uint64_t, LayerStateField::Buffer> mFrameNumber{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->frameNumber; }};
+
     int64_t mFramesSinceBufferUpdate = 0;
 
     OutputLayerState<half4, LayerStateField::SolidColor>
@@ -453,7 +459,7 @@
                                       return hash;
                                   }};
 
-    static const constexpr size_t kNumNonUniqueFields = 16;
+    static const constexpr size_t kNumNonUniqueFields = 17;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -472,6 +478,7 @@
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
+                &mFrameNumber,
         };
     }
 };
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 0b1c262..ae52ba2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -359,6 +359,30 @@
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
 }
 
+TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.buffer = new GraphicBuffer();
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.buffer = new GraphicBuffer();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+
+    for (uint64_t i = 0; i < 10; i++) {
+        layerFECompositionStateTwo.frameNumber = i;
+        setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                           layerFECompositionStateTwo);
+        Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
+    }
+}
+
 TEST_F(LayerStateTest, compareBuffer) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 4de8dc2..aeaf1e1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2266,14 +2266,14 @@
 }
 
 void Layer::handleDropInputMode(gui::WindowInfo& info) const {
-    if (mDrawingState.inputInfo.inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL)) {
+    if (mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
         return;
     }
 
     // Check if we need to drop input unconditionally
     gui::DropInputMode dropInputMode = getDropInputMode();
     if (dropInputMode == gui::DropInputMode::ALL) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy.", getDebugName());
         return;
     }
@@ -2286,7 +2286,7 @@
     // Check if the parent has set an alpha on the layer
     sp<Layer> parent = mDrawingParent.promote();
     if (parent && parent->getAlpha() != 1.0_hf) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy because alpha=%f", getDebugName(),
               static_cast<float>(getAlpha()));
     }
@@ -2294,7 +2294,7 @@
     // Check if the parent has cropped the buffer
     Rect bufferSize = getCroppedBufferSize(getDrawingState());
     if (!bufferSize.isValid()) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT_IF_OBSCURED;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
         return;
     }
 
@@ -2306,7 +2306,7 @@
     bool croppedByParent = bufferInScreenSpace != Rect{mScreenBounds};
 
     if (croppedByParent) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
               getDebugName());
     } else {
@@ -2314,7 +2314,7 @@
         // input if the window is obscured. This check should be done in surfaceflinger but the
         // logic currently resides in inputflinger. So pass the if_obscured check to input to only
         // drop input events if the window is obscured.
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT_IF_OBSCURED;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
     }
 }
 
@@ -2323,7 +2323,7 @@
         mDrawingState.inputInfo.name = getName();
         mDrawingState.inputInfo.ownerUid = mOwnerUid;
         mDrawingState.inputInfo.ownerPid = mOwnerPid;
-        mDrawingState.inputInfo.inputFeatures = WindowInfo::Feature::NO_INPUT_CHANNEL;
+        mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL;
         mDrawingState.inputInfo.displayId = getLayerStack().id;
     }
 
@@ -2351,7 +2351,7 @@
     // If the window will be blacked out on a display because the display does not have the secure
     // flag and the layer has the secure flag set, then drop input.
     if (!displayIsSecure && isSecure()) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
     }
 
     auto cropLayer = mDrawingState.touchableRegionCrop.promote();
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 43a6e55..2ece51c 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -3,4 +3,5 @@
 chaviw@google.com
 lpy@google.com
 racarr@google.com
-vishnun@google.com
+scroggo@google.com
+vishnun@google.com
\ No newline at end of file
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e8034de..d4979e8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3674,11 +3674,12 @@
     return false;
 }
 
-void SurfaceFlinger::flushPendingTransactionQueues(
+int SurfaceFlinger::flushPendingTransactionQueues(
         std::vector<TransactionState>& transactions,
-        std::unordered_set<sp<IBinder>, SpHash<IBinder>>& bufferLayersReadyToPresent,
+        std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
         std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
         bool tryApplyUnsignaled) {
+    int transactionsPendingBarrier = 0;
     auto it = mPendingTransactionQueues.begin();
     while (it != mPendingTransactionQueues.end()) {
         auto& [applyToken, transactionQueue] = *it;
@@ -3701,8 +3702,21 @@
                 setTransactionFlags(eTransactionFlushNeeded);
                 break;
             }
+            if (ready == TransactionReadiness::NotReadyBarrier) {
+                transactionsPendingBarrier++;
+                setTransactionFlags(eTransactionFlushNeeded);
+                break;
+            }
             transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
-                bufferLayersReadyToPresent.insert(state.surface);
+                const bool frameNumberChanged = 
+                    state.bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+                if (frameNumberChanged) {
+                    bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber;
+                } else {
+                    // Barrier function only used for BBQ which always includes a frame number
+                    bufferLayersReadyToPresent[state.surface] =
+                        std::numeric_limits<uint64_t>::max();
+                }
             });
             const bool appliedUnsignaled = (ready == TransactionReadiness::ReadyUnsignaled);
             if (appliedUnsignaled) {
@@ -3720,6 +3734,7 @@
             it = std::next(it, 1);
         }
     }
+    return transactionsPendingBarrier;
 }
 
 bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) {
@@ -3728,19 +3743,21 @@
     // states) around outside the scope of the lock
     std::vector<TransactionState> transactions;
     // Layer handles that have transactions with buffers that are ready to be applied.
-    std::unordered_set<sp<IBinder>, SpHash<IBinder>> bufferLayersReadyToPresent;
+    std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>> bufferLayersReadyToPresent;
     std::unordered_set<sp<IBinder>, SpHash<IBinder>> applyTokensWithUnsignaledTransactions;
     {
         Mutex::Autolock _l(mStateLock);
         {
             Mutex::Autolock _l(mQueueLock);
 
+            int lastTransactionsPendingBarrier = 0;
+            int transactionsPendingBarrier = 0;
             // First collect transactions from the pending transaction queues.
             // We are not allowing unsignaled buffers here as we want to
             // collect all the transactions from applyTokens that are ready first.
-            flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
-                                          applyTokensWithUnsignaledTransactions,
-                                          /*tryApplyUnsignaled*/ false);
+            transactionsPendingBarrier =
+                    flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                            applyTokensWithUnsignaledTransactions, /*tryApplyUnsignaled*/ false);
 
             // Second, collect transactions from the transaction queue.
             // Here as well we are not allowing unsignaled buffers for the same
@@ -3765,18 +3782,48 @@
                                                          /*tryApplyUnsignaled*/ false);
                 }();
                 ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
-                if (ready == TransactionReadiness::NotReady) {
+                if (ready != TransactionReadiness::Ready) {
+                    if (ready == TransactionReadiness::NotReadyBarrier) {
+                        transactionsPendingBarrier++;
+                    }
                     mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction));
                 } else {
                     transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
-                        bufferLayersReadyToPresent.insert(state.surface);
-                    });
+                        const bool frameNumberChanged = 
+                            state.bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+                        if (frameNumberChanged) {
+                            bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber;
+                        } else {
+                            // Barrier function only used for BBQ which always includes a frame number.
+                            // This value only used for barrier logic.
+                            bufferLayersReadyToPresent[state.surface] =
+                                std::numeric_limits<uint64_t>::max();
+                        }
+                      });
                     transactions.emplace_back(std::move(transaction));
                 }
                 mTransactionQueue.pop_front();
                 ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
 
+            // Transactions with a buffer pending on a barrier may be on a different applyToken
+            // than the transaction which satisfies our barrier. In fact this is the exact use case
+            // that the primitive is designed for. This means we may first process
+            // the barrier dependent transaction, determine it ineligible to complete
+            // and then satisfy in a later inner iteration of flushPendingTransactionQueues.
+            // The barrier dependent transaction was eligible to be presented in this frame
+            // but we would have prevented it without case. To fix this we continually
+            // loop through flushPendingTransactionQueues until we perform an iteration
+            // where the number of transactionsPendingBarrier doesn't change. This way
+            // we can continue to resolve dependency chains of barriers as far as possible.
+            while (lastTransactionsPendingBarrier != transactionsPendingBarrier) {
+                lastTransactionsPendingBarrier = transactionsPendingBarrier;
+                transactionsPendingBarrier =
+                    flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                        applyTokensWithUnsignaledTransactions,
+                        /*tryApplyUnsignaled*/ false);
+            }
+
             // We collected all transactions that could apply without latching unsignaled buffers.
             // If we are allowing latch unsignaled of some form, now it's the time to go over the
             // transactions that were not applied and try to apply them unsignaled.
@@ -3892,7 +3939,8 @@
 auto SurfaceFlinger::transactionIsReadyToBeApplied(
         const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime,
         uid_t originUid, const Vector<ComposerState>& states,
-        const std::unordered_set<sp<IBinder>, SpHash<IBinder>>& bufferLayersReadyToPresent,
+        const std::unordered_map<
+            sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
         size_t totalTXapplied, bool tryApplyUnsignaled) const -> TransactionReadiness {
     ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, info.vsyncId);
     const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
@@ -3930,6 +3978,17 @@
             continue;
         }
 
+        if (s.hasBufferChanges() && s.bufferData->hasBarrier &&
+            ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) {
+            const bool willApplyBarrierFrame =
+                (bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end()) &&
+                (bufferLayersReadyToPresent.at(s.surface) >= s.bufferData->barrierFrameNumber);
+            if (!willApplyBarrierFrame) {
+                ATRACE_NAME("NotReadyBarrier");
+                return TransactionReadiness::NotReadyBarrier;
+            }
+        }
+
         const bool allowLatchUnsignaled = tryApplyUnsignaled &&
                 shouldLatchUnsignaled(layer, s, states.size(), totalTXapplied);
         ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(),
@@ -3950,8 +4009,8 @@
         if (s.hasBufferChanges()) {
             // If backpressure is enabled and we already have a buffer to commit, keep the
             // transaction in the queue.
-            const bool hasPendingBuffer =
-                    bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end();
+            const bool hasPendingBuffer = bufferLayersReadyToPresent.find(s.surface) !=
+                bufferLayersReadyToPresent.end();
             if (layer->backpressureEnabled() && hasPendingBuffer && isAutoTimestamp) {
                 ATRACE_NAME("hasPendingBuffer");
                 return TransactionReadiness::NotReady;
@@ -6063,7 +6122,6 @@
     static bool updateOverlay =
             property_get_bool("debug.sf.kernel_idle_timer_update_overlay", true);
     if (!updateOverlay) return;
-    if (Mutex::Autolock lock(mStateLock); !isRefreshRateOverlayEnabled()) return;
 
     // Update the overlay on the main thread to avoid race conditions with
     // mRefreshRateConfigs->getCurrentRefreshRate()
@@ -6073,6 +6131,7 @@
             ALOGW("%s: default display is null", __func__);
             return;
         }
+        if (!display->isRefreshRateOverlayEnabled()) return;
 
         const auto desiredActiveMode = display->getDesiredActiveMode();
         const std::optional<DisplayModeId> desiredModeId = desiredActiveMode
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 5829838..0365b37 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -760,9 +760,9 @@
     // Returns true if there is at least one transaction that needs to be flushed
     bool transactionFlushNeeded();
 
-    void flushPendingTransactionQueues(
+    int flushPendingTransactionQueues(
             std::vector<TransactionState>& transactions,
-            std::unordered_set<sp<IBinder>, SpHash<IBinder>>& bufferLayersReadyToPresent,
+            std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
             std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
             bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock);
 
@@ -789,13 +789,15 @@
     void commitOffscreenLayers();
     enum class TransactionReadiness {
         NotReady,
+        NotReadyBarrier,
         Ready,
         ReadyUnsignaled,
     };
     TransactionReadiness transactionIsReadyToBeApplied(
             const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime,
             uid_t originUid, const Vector<ComposerState>& states,
-            const std::unordered_set<sp<IBinder>, SpHash<IBinder>>& bufferLayersReadyToPresent,
+            const std::unordered_map<
+                sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
             size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock);
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t&, size_t numStates,
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index ff3e1c5..aa6c74e 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_test {
     name: "transactiontrace_testsuite",
     defaults: [
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 58cd7ba..1eea023 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -89,6 +89,8 @@
         "LayerHistoryTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
+        "LayerTest.cpp",
+        "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerTest.cpp b/services/surfaceflinger/tests/unittests/LayerTest.cpp
new file mode 100644
index 0000000..4974f90
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTest.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <EffectLayer.h>
+#include <gtest/gtest.h>
+#include <ui/FloatRect.h>
+#include <ui/Transform.h>
+#include <limits>
+
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+namespace {
+
+class LayerTest : public BaseLayerTest {
+protected:
+    static constexpr const float MIN_FLOAT = std::numeric_limits<float>::min();
+    static constexpr const float MAX_FLOAT = std::numeric_limits<float>::max();
+    static constexpr const FloatRect LARGE_FLOAT_RECT{MIN_FLOAT, MIN_FLOAT, MAX_FLOAT, MAX_FLOAT};
+};
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, LayerTest,
+                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
+                                         std::make_shared<EffectLayerFactory>()),
+                         PrintToStringParamName);
+
+TEST_P(LayerTest, layerVisibleByDefault) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+    ASSERT_FALSE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithZeroMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    layer_state_t::matrix22_t matrix{0, 0, 0, 0};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithInfMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    constexpr const float INF = std::numeric_limits<float>::infinity();
+    layer_state_t::matrix22_t matrix{INF, 0, 0, INF};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithNanMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    constexpr const float QUIET_NAN = std::numeric_limits<float>::quiet_NaN();
+    layer_state_t::matrix22_t matrix{QUIET_NAN, 0, 0, QUIET_NAN};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
new file mode 100644
index 0000000..5a2c147
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LayerTestUtils.h"
+
+#include "mock/MockEventThread.h"
+
+namespace android {
+
+using testing::_;
+using testing::Return;
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+sp<Layer> BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
+    sp<Client> client;
+    LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS,
+                           LayerMetadata());
+    return new BufferStateLayer(args);
+}
+
+sp<Layer> EffectLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
+    sp<Client> client;
+    LayerCreationArgs args(flinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
+    return new EffectLayer(args);
+}
+
+std::string PrintToStringParamName(
+        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info) {
+    return info.param->name();
+}
+
+BaseLayerTest::BaseLayerTest() {
+    setupScheduler();
+}
+
+void BaseLayerTest::setupScheduler() {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                            TestableSurfaceFlinger::kTwoDisplayModes);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
new file mode 100644
index 0000000..fc9b6a2
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include "BufferStateLayer.h"
+#include "EffectLayer.h"
+#include "Layer.h"
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class LayerFactory {
+public:
+    virtual ~LayerFactory() = default;
+
+    virtual std::string name() = 0;
+    virtual sp<Layer> createLayer(TestableSurfaceFlinger& flinger) = 0;
+
+protected:
+    static constexpr uint32_t WIDTH = 100;
+    static constexpr uint32_t HEIGHT = 100;
+    static constexpr uint32_t LAYER_FLAGS = 0;
+};
+
+class BufferStateLayerFactory : public LayerFactory {
+public:
+    std::string name() override { return "BufferStateLayer"; }
+    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override;
+};
+
+class EffectLayerFactory : public LayerFactory {
+public:
+    std::string name() override { return "EffectLayer"; }
+    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override;
+};
+
+std::string PrintToStringParamName(
+        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info);
+
+class BaseLayerTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
+protected:
+    BaseLayerTest();
+
+    void setupScheduler();
+
+    TestableSurfaceFlinger mFlinger;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 2b69f13..825f145 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -30,73 +30,29 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 #include "FpsOps.h"
+#include "LayerTestUtils.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
 #include "mock/MockVsyncController.h"
 
 namespace android {
 
-using testing::_;
 using testing::DoAll;
 using testing::Mock;
-using testing::Return;
 using testing::SetArgPointee;
 
 using android::Hwc2::IComposer;
 using android::Hwc2::IComposerClient;
 
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-
 using scheduler::LayerHistory;
 
 using FrameRate = Layer::FrameRate;
 using FrameRateCompatibility = Layer::FrameRateCompatibility;
 
-class LayerFactory {
-public:
-    virtual ~LayerFactory() = default;
-
-    virtual std::string name() = 0;
-    virtual sp<Layer> createLayer(TestableSurfaceFlinger& flinger) = 0;
-
-protected:
-    static constexpr uint32_t WIDTH = 100;
-    static constexpr uint32_t HEIGHT = 100;
-    static constexpr uint32_t LAYER_FLAGS = 0;
-};
-
-class BufferStateLayerFactory : public LayerFactory {
-public:
-    std::string name() override { return "BufferStateLayer"; }
-    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
-        sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS,
-                               LayerMetadata());
-        return new BufferStateLayer(args);
-    }
-};
-
-class EffectLayerFactory : public LayerFactory {
-public:
-    std::string name() override { return "EffectLayer"; }
-    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
-        sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "color-layer", LAYER_FLAGS,
-                               LayerMetadata());
-        return new EffectLayer(args);
-    }
-};
-
-std::string PrintToStringParamName(
-        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info) {
-    return info.param->name();
-}
-
 /**
  * This class tests the behaviour of Layer::SetFrameRate and Layer::GetFrameRate
  */
-class SetFrameRateTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
+class SetFrameRateTest : public BaseLayerTest {
 protected:
     const FrameRate FRAME_RATE_VOTE1 = FrameRate(67_Hz, FrameRateCompatibility::Default);
     const FrameRate FRAME_RATE_VOTE2 = FrameRate(14_Hz, FrameRateCompatibility::ExactOrMultiple);
@@ -106,14 +62,10 @@
 
     SetFrameRateTest();
 
-    void setupScheduler();
-
     void addChild(sp<Layer> layer, sp<Layer> child);
     void removeChild(sp<Layer> layer, sp<Layer> child);
     void commitTransaction();
 
-    TestableSurfaceFlinger mFlinger;
-
     std::vector<sp<Layer>> mLayers;
 };
 
@@ -122,8 +74,6 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    setupScheduler();
-
     mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
 }
 
@@ -142,33 +92,6 @@
     }
 }
 
-void SetFrameRateTest::setupScheduler() {
-    auto eventThread = std::make_unique<mock::EventThread>();
-    auto sfEventThread = std::make_unique<mock::EventThread>();
-
-    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
-                                                       ResyncCallback())));
-
-    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
-                                                       ResyncCallback())));
-
-    auto vsyncController = std::make_unique<mock::VsyncController>();
-    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-    EXPECT_CALL(*vsyncTracker, currentPeriod())
-            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                            std::move(eventThread), std::move(sfEventThread),
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
-                            TestableSurfaceFlinger::kTwoDisplayModes);
-}
-
 namespace {
 
 TEST_P(SetFrameRateTest, SetAndGet) {