Merge "Rounded corner child clipping tests"
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index 8dd95cf..2b8cc57 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -225,6 +225,11 @@
     virtual bool updateInfo() = 0;
 
     /**
+     * Updates from another input window handle.
+     */
+    void updateFrom(const sp<InputWindowHandle> handle);
+
+    /**
      * Releases the channel used by the associated information when it is
      * no longer needed.
      */
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index 28d0e4f..49a9414 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -89,6 +89,15 @@
     return false;
 }
 
+int32_t ActivityManager::getUidProcessState(const uid_t uid, const String16& callingPackage)
+{
+    sp<IActivityManager> service = getService();
+    if (service != nullptr) {
+        return service->getUidProcessState(uid, callingPackage);
+    }
+    return PROCESS_STATE_UNKNOWN;
+}
+
 status_t ActivityManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient) {
     sp<IActivityManager> service = getService();
     if (service != nullptr) {
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 428db4d..377f604 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -17,8 +17,8 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+#include <binder/ActivityManager.h>
 #include <binder/IActivityManager.h>
-
 #include <binder/Parcel.h>
 
 namespace android {
@@ -90,6 +90,20 @@
          if (reply.readExceptionCode() != 0) return false;
          return reply.readInt32() == 1;
     }
+
+    virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+        data.writeInt32(uid);
+        data.writeString16(callingPackage);
+        remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
+        // fail on exception
+        if (reply.readExceptionCode() != 0) {
+            return ActivityManager::PROCESS_STATE_UNKNOWN;
+        }
+        return reply.readInt32();
+    }
 };
 
 // ------------------------------------------------------------------------------------
diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp
index 697e948..82f9047 100644
--- a/libs/binder/IUidObserver.cpp
+++ b/libs/binder/IUidObserver.cpp
@@ -55,6 +55,16 @@
         data.writeInt32(disabled ? 1 : 0);
         remote()->transact(ON_UID_IDLE_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
+
+    virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IUidObserver::getInterfaceDescriptor());
+        data.writeInt32((int32_t) uid);
+        data.writeInt32(procState);
+        data.writeInt64(procStateSeq);
+        remote()->transact(ON_UID_STATE_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
+    }
 };
 
 // ----------------------------------------------------------------------
@@ -89,6 +99,14 @@
             onUidIdle(uid, disabled);
             return NO_ERROR;
         } break;
+        case ON_UID_STATE_CHANGED_TRANSACTION: {
+            CHECK_INTERFACE(IUidObserver, data, reply);
+            uid_t uid = data.readInt32();
+            int32_t procState = data.readInt32();
+            int64_t procStateSeq = data.readInt64();
+            onUidStateChanged(uid, procState, procStateSeq);
+            return NO_ERROR;
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/binder/include/binder/ActivityManager.h b/libs/binder/include/binder/ActivityManager.h
index b8db091..26dafd0 100644
--- a/libs/binder/include/binder/ActivityManager.h
+++ b/libs/binder/include/binder/ActivityManager.h
@@ -31,6 +31,8 @@
 public:
 
     enum {
+        // Flag for registerUidObserver: report uid state changed
+        UID_OBSERVER_PROCSTATE = 1<<0,
         // Flag for registerUidObserver: report uid gone
         UID_OBSERVER_GONE = 1<<1,
         // Flag for registerUidObserver: report uid has become idle
@@ -40,8 +42,27 @@
     };
 
     enum {
-        // Not a real process state
-        PROCESS_STATE_UNKNOWN = -1
+        PROCESS_STATE_UNKNOWN = -1,
+        PROCESS_STATE_PERSISTENT = 0,
+        PROCESS_STATE_PERSISTENT_UI = 1,
+        PROCESS_STATE_TOP = 2,
+        PROCESS_STATE_FOREGROUND_SERVICE = 3,
+        PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 4,
+        PROCESS_STATE_IMPORTANT_FOREGROUND = 5,
+        PROCESS_STATE_IMPORTANT_BACKGROUND = 6,
+        PROCESS_STATE_TRANSIENT_BACKGROUND = 7,
+        PROCESS_STATE_BACKUP = 8,
+        PROCESS_STATE_SERVICE = 9,
+        PROCESS_STATE_RECEIVER = 10,
+        PROCESS_STATE_TOP_SLEEPING = 11,
+        PROCESS_STATE_HEAVY_WEIGHT = 12,
+        PROCESS_STATE_HOME = 13,
+        PROCESS_STATE_LAST_ACTIVITY = 14,
+        PROCESS_STATE_CACHED_ACTIVITY = 15,
+        PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16,
+        PROCESS_STATE_CACHED_RECENT = 17,
+        PROCESS_STATE_CACHED_EMPTY = 18,
+        PROCESS_STATE_NONEXISTENT = 19,
     };
 
     ActivityManager();
@@ -53,8 +74,10 @@
                              const String16& callingPackage);
     void unregisterUidObserver(const sp<IUidObserver>& observer);
     bool isUidActive(const uid_t uid, const String16& callingPackage);
+    int getUidProcessState(const uid_t uid, const String16& callingPackage);
 
-    status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
+
+  status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
     status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
 
 private:
diff --git a/libs/binder/include/binder/IActivityManager.h b/libs/binder/include/binder/IActivityManager.h
index f34969b..6abc071 100644
--- a/libs/binder/include/binder/IActivityManager.h
+++ b/libs/binder/include/binder/IActivityManager.h
@@ -38,12 +38,14 @@
                                      const String16& callingPackage) = 0;
     virtual void unregisterUidObserver(const sp<IUidObserver>& observer) = 0;
     virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0;
+    virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0;
 
     enum {
         OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
         REGISTER_UID_OBSERVER_TRANSACTION,
         UNREGISTER_UID_OBSERVER_TRANSACTION,
-        IS_UID_ACTIVE_TRANSACTION
+        IS_UID_ACTIVE_TRANSACTION,
+        GET_UID_PROCESS_STATE_TRANSACTION
     };
 };
 
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 75b348c..26e8c0b 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -132,8 +132,10 @@
             bool                isServingCall() const;
 
             // The work source represents the UID of the process we should attribute the transaction
-            // to.
-            // We use -1 to specify that the work source was not set using #setWorkSource.
+            // to. We use -1 to specify that the work source was not set using #setWorkSource.
+            //
+            // This constant needs to be kept in sync with Binder.UNSET_WORKSOURCE from the Java
+            // side.
             static const int32_t kUnsetWorkSource = -1;
 
 private:
diff --git a/libs/binder/include/binder/IUidObserver.h b/libs/binder/include/binder/IUidObserver.h
index 9937ad6..a1f530d 100644
--- a/libs/binder/include/binder/IUidObserver.h
+++ b/libs/binder/include/binder/IUidObserver.h
@@ -34,11 +34,13 @@
     virtual void onUidGone(uid_t uid, bool disabled) = 0;
     virtual void onUidActive(uid_t uid) = 0;
     virtual void onUidIdle(uid_t uid, bool disabled) = 0;
+    virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq) = 0;
 
     enum {
         ON_UID_GONE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
         ON_UID_ACTIVE_TRANSACTION,
-        ON_UID_IDLE_TRANSACTION
+        ON_UID_IDLE_TRANSACTION,
+        ON_UID_STATE_CHANGED_TRANSACTION
     };
 };
 
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 80d435f..7d26151 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -636,6 +636,21 @@
         *outComponentMask = static_cast<uint8_t>(value);
         return error;
     }
+
+    virtual status_t setDisplayContentSamplingEnabled(const sp<IBinder>& display, bool enable,
+                                                      uint8_t componentMask,
+                                                      uint64_t maxFrames) const {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        data.writeStrongBinder(display);
+        data.writeBool(enable);
+        data.writeByte(static_cast<int8_t>(componentMask));
+        data.writeUint64(maxFrames);
+        status_t result =
+                remote()->transact(BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED, data,
+                                   &reply);
+        return result;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -1004,6 +1019,42 @@
             }
             return result;
         }
+        case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+
+            sp<IBinder> display = nullptr;
+            bool enable = false;
+            int8_t componentMask = 0;
+            uint64_t maxFrames = 0;
+            status_t result = data.readStrongBinder(&display);
+            if (result != NO_ERROR) {
+                ALOGE("setDisplayContentSamplingEnabled failure in reading Display token: %d",
+                      result);
+                return result;
+            }
+
+            result = data.readBool(&enable);
+            if (result != NO_ERROR) {
+                ALOGE("setDisplayContentSamplingEnabled failure in reading enable: %d", result);
+                return result;
+            }
+
+            result = data.readByte(static_cast<int8_t*>(&componentMask));
+            if (result != NO_ERROR) {
+                ALOGE("setDisplayContentSamplingEnabled failure in reading component mask: %d",
+                      result);
+                return result;
+            }
+
+            result = data.readUint64(&maxFrames);
+            if (result != NO_ERROR) {
+                ALOGE("setDisplayContentSamplingEnabled failure in reading max frames: %d", result);
+                return result;
+            }
+
+            return setDisplayContentSamplingEnabled(display, enable,
+                                                    static_cast<uint8_t>(componentMask), maxFrames);
+        }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 9dfccc7..405d228 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1098,6 +1098,14 @@
                                                     outComponentMask);
 }
 
+status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const sp<IBinder>& display,
+                                                                 bool enable, uint8_t componentMask,
+                                                                 uint64_t maxFrames) {
+    return ComposerService::getComposerService()->setDisplayContentSamplingEnabled(display, enable,
+                                                                                   componentMask,
+                                                                                   maxFrames);
+}
+
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::capture(const sp<IBinder>& display, const ui::Dataspace reqDataSpace,
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 3b6c6e4..41369c8 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -300,6 +300,14 @@
                                                            ui::PixelFormat* outFormat,
                                                            ui::Dataspace* outDataspace,
                                                            uint8_t* outComponentMask) const = 0;
+
+    /* Turns on the color sampling engine on the display.
+     *
+     * Requires the ACCESS_SURFACE_FLINGER permission.
+     */
+    virtual status_t setDisplayContentSamplingEnabled(const sp<IBinder>& display, bool enable,
+                                                      uint8_t componentMask,
+                                                      uint64_t maxFrames) const = 0;
 };
 
 // ----------------------------------------------------------------------------
@@ -340,6 +348,7 @@
         GET_COMPOSITION_PREFERENCE,
         GET_COLOR_MANAGEMENT,
         GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES,
+        SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
     };
 
     virtual status_t onTransact(uint32_t code, const Parcel& data,
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 7d05512..ba943a0 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -386,6 +386,8 @@
                                                           ui::PixelFormat* outFormat,
                                                           ui::Dataspace* outDataspace,
                                                           uint8_t* outComponentMask);
+    static status_t setDisplayContentSamplingEnabled(const sp<IBinder>& display, bool enable,
+                                                     uint8_t componentMask, uint64_t maxFrames);
 
 private:
     virtual void onFirstRef();
diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp
index f2c0e0c..f9d5dd6 100644
--- a/libs/gui/tests/DisplayedContentSampling_test.cpp
+++ b/libs/gui/tests/DisplayedContentSampling_test.cpp
@@ -36,7 +36,12 @@
         ASSERT_TRUE(mDisplayToken);
     }
 
-    bool shouldSkipTest(status_t status) {
+    bool shouldSkipTest() {
+        ui::PixelFormat format;
+        ui::Dataspace dataspace;
+        status_t status =
+                mComposerClient->getDisplayedContentSamplingAttributes(mDisplayToken, &format,
+                                                                       &dataspace, &componentMask);
         if (status == PERMISSION_DENIED) {
             SUCCEED() << "permissions denial, skipping test";
             return true;
@@ -50,19 +55,53 @@
 
     sp<SurfaceComposerClient> mComposerClient;
     sp<IBinder> mDisplayToken;
+    uint8_t componentMask = 0;
 };
 
 TEST_F(DisplayedContentSamplingTest, GetDisplayedContentSamplingAttributesAreSane) {
+    // tradefed infrastructure does not support use of GTEST_SKIP
+    if (shouldSkipTest()) return;
+
     ui::PixelFormat format;
     ui::Dataspace dataspace;
-    uint8_t componentMask = 0;
     status_t status =
             mComposerClient->getDisplayedContentSamplingAttributes(mDisplayToken, &format,
                                                                    &dataspace, &componentMask);
-    if (shouldSkipTest(status)) {
-        return;
-    }
     EXPECT_EQ(OK, status);
     EXPECT_LE(componentMask, INVALID_MASK);
 }
+
+TEST_F(DisplayedContentSamplingTest, EnableWithInvalidMaskReturnsBadValue) {
+    if (shouldSkipTest()) return;
+
+    status_t status =
+            mComposerClient->setDisplayContentSamplingEnabled(mDisplayToken, true, INVALID_MASK, 0);
+    EXPECT_EQ(BAD_VALUE, status);
+}
+
+TEST_F(DisplayedContentSamplingTest, EnableAndDisableSucceed) {
+    if (shouldSkipTest()) return;
+
+    status_t status = mComposerClient->setDisplayContentSamplingEnabled(mDisplayToken, true,
+                                                                        componentMask, 10);
+    EXPECT_EQ(OK, status);
+
+    status = mComposerClient->setDisplayContentSamplingEnabled(mDisplayToken, false, componentMask,
+                                                               0);
+    EXPECT_EQ(OK, status);
+}
+
+TEST_F(DisplayedContentSamplingTest, SelectivelyDisableComponentOk) {
+    if (shouldSkipTest()) return;
+
+    status_t status = mComposerClient->setDisplayContentSamplingEnabled(mDisplayToken, true,
+                                                                        componentMask, 0);
+    EXPECT_EQ(OK, status);
+
+    // Clear the lowest bit.
+    componentMask &= (componentMask - 1);
+    status = mComposerClient->setDisplayContentSamplingEnabled(mDisplayToken, false, componentMask,
+                                                               0);
+    EXPECT_EQ(OK, status);
+}
 } // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 3950bb6..cb1756f 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -647,6 +647,11 @@
                                                    uint8_t* /*outComponentMask*/) const override {
         return NO_ERROR;
     }
+    status_t setDisplayContentSamplingEnabled(const sp<IBinder>& /*display*/, bool /*enable*/,
+                                              uint8_t /*componentMask*/,
+                                              uint64_t /*maxFrames*/) const override {
+        return NO_ERROR;
+    }
 
     virtual status_t getColorManagement(bool* /*outGetColorManagement*/) const { return NO_ERROR; }
 
diff --git a/libs/input/InputWindow.cpp b/libs/input/InputWindow.cpp
index 556a005..aa1371f 100644
--- a/libs/input/InputWindow.cpp
+++ b/libs/input/InputWindow.cpp
@@ -162,4 +162,8 @@
     return mInfo.token;
 }
 
+void InputWindowHandle::updateFrom(sp<InputWindowHandle> handle) {
+    mInfo = handle->mInfo;
+}
+
 } // namespace android
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 5941cdf..aa4e319 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -29,32 +29,32 @@
 struct DisplaySettings {
     // Rectangle describing the physical display. We will project from the
     // logical clip onto this rectangle.
-    Rect physicalDisplay;
+    Rect physicalDisplay = Rect::INVALID_RECT;
 
     // Rectangle bounded by the x,y- clipping planes in the logical display, so
     // that the orthographic projection matrix can be computed. When
     // constructing this matrix, z-coordinate bound are assumed to be at z=0 and
     // z=1.
-    Rect clip;
+    Rect clip = Rect::INVALID_RECT;
 
     // Global transform to apply to all layers.
-    mat4 globalTransform;
+    mat4 globalTransform = mat4();
 
     // Maximum luminance pulled from the display's HDR capabilities.
-    float maxLuminence;
+    float maxLuminence = 1.0f;
 
     // Output dataspace that will be populated if wide color gamut is used, or
     // DataSpace::UNKNOWN otherwise.
-    ui::Dataspace outputDataspace;
+    ui::Dataspace outputDataspace = ui::Dataspace::UNKNOWN;
 
     // Additional color transform to apply in linear space after transforming
     // to the output dataspace.
-    mat4 colorTransform;
+    mat4 colorTransform = mat4();
 
     // Region that will be cleared to (0, 0, 0, 0) prior to rendering.
     // clearRegion will first be transformed by globalTransform so that it will
     // be in the same coordinate space as the rendered layers.
-    Region clearRegion;
+    Region clearRegion = Region::INVALID_REGION;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index facea21..38dee40 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -34,58 +34,58 @@
     // Buffer containing the image that we will render.
     // If buffer == nullptr, then the rest of the fields in this struct will be
     // ignored.
-    sp<GraphicBuffer> buffer;
+    sp<GraphicBuffer> buffer = nullptr;
 
     // Texture identifier to bind the external texture to.
     // TODO(alecmouri): This is GL-specific...make the type backend-agnostic.
-    uint32_t textureName;
+    uint32_t textureName = 0;
 
     // Whether to use filtering when rendering the texture.
-    bool useTextureFiltering;
+    bool useTextureFiltering = false;
 
     // Transform matrix to apply to texture coordinates.
-    mat4 textureTransform;
+    mat4 textureTransform = mat4();
 
     // Wheteher to use pre-multiplied alpha
-    bool usePremultipliedAlpha;
+    bool usePremultipliedAlpha = true;
 
     // HDR color-space setting for Y410.
-    bool isY410BT2020;
+    bool isY410BT2020 = false;
 };
 
 // Metadata describing the layer geometry.
 struct Geometry {
     // Boundaries of the layer.
-    FloatRect boundaries;
+    FloatRect boundaries = FloatRect();
 
     // Transform matrix to apply to mesh coordinates.
-    mat4 positionTransform;
+    mat4 positionTransform = mat4();
 };
 
 // Descriptor of the source pixels for this layer.
 struct PixelSource {
     // Source buffer
-    Buffer buffer;
+    Buffer buffer = Buffer();
 
     // The solid color with which to fill the layer.
     // This should only be populated if we don't render from an application
     // buffer.
-    half3 solidColor;
+    half3 solidColor = half3(0.0f, 0.0f, 0.0f);
 };
 
 // The settings that RenderEngine requires for correctly rendering a Layer.
 struct LayerSettings {
     // Geometry information
-    Geometry geometry;
+    Geometry geometry = Geometry();
 
     // Source pixels for this layer.
-    PixelSource source;
+    PixelSource source = PixelSource();
 
     // Alpha option to apply to the source pixels
-    half alpha;
+    half alpha = half(0.0);
 
     // Color space describing how the source pixels should be interpreted.
-    ui::Dataspace sourceDataspace;
+    ui::Dataspace sourceDataspace = ui::Dataspace::UNKNOWN;
 };
 
 } // namespace renderengine
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index f770975..a4d0dd1 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -55,6 +55,7 @@
     srcs: [
         "ColorSpace.cpp",
         "BufferHubBuffer.cpp",
+        "BufferHubEventFd.cpp",
         "BufferHubMetadata.cpp",
         "DebugUtils.cpp",
         "Fence.cpp",
@@ -112,6 +113,7 @@
             cflags: ["-DLIBUI_IN_VNDK"],
             exclude_srcs: [
                 "BufferHubBuffer.cpp",
+                "BufferHubEventFd.cpp",
                 "BufferHubMetadata.cpp",
             ],
             exclude_header_libs: [
diff --git a/libs/ui/BufferHubEventFd.cpp b/libs/ui/BufferHubEventFd.cpp
new file mode 100644
index 0000000..978b352
--- /dev/null
+++ b/libs/ui/BufferHubEventFd.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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 <sys/eventfd.h>
+
+#include <log/log.h>
+#include <ui/BufferHubEventFd.h>
+
+namespace android {
+
+BufferHubEventFd::BufferHubEventFd() : mFd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) {}
+
+status_t BufferHubEventFd::signal() const {
+    if (!isValid()) {
+        ALOGE("%s: cannot signal an invalid eventfd.", __FUNCTION__);
+        return DEAD_OBJECT;
+    }
+
+    eventfd_write(mFd.get(), 1);
+    return OK;
+}
+
+status_t BufferHubEventFd::clear() const {
+    if (!isValid()) {
+        ALOGE("%s: cannot clear an invalid eventfd.", __FUNCTION__);
+        return DEAD_OBJECT;
+    }
+
+    eventfd_t value;
+    eventfd_read(mFd.get(), &value);
+    return OK;
+}
+
+} // namespace android
diff --git a/libs/ui/Rect.cpp b/libs/ui/Rect.cpp
index d8702e5..13fed3a 100644
--- a/libs/ui/Rect.cpp
+++ b/libs/ui/Rect.cpp
@@ -72,6 +72,14 @@
     return *this;
 }
 
+Rect& Rect::inset(int32_t _left, int32_t _top, int32_t _right, int32_t _bottom) {
+    this->left += _left;
+    this->top += _top;
+    this->right -= _right;
+    this->bottom -= _bottom;
+    return *this;
+}
+
 const Rect Rect::operator +(const Point& rhs) const {
     const Rect result(left + rhs.x, top + rhs.y, right + rhs.x, bottom + rhs.y);
     return result;
diff --git a/libs/ui/include/ui/BufferHubEventFd.h b/libs/ui/include/ui/BufferHubEventFd.h
new file mode 100644
index 0000000..0e856bd
--- /dev/null
+++ b/libs/ui/include/ui/BufferHubEventFd.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ANDROID_BUFFER_HUB_EVENT_FD_H_
+#define ANDROID_BUFFER_HUB_EVENT_FD_H_
+
+#include <android-base/unique_fd.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+class BufferHubEventFd {
+public:
+    /**
+     * Constructs a valid event fd.
+     */
+    BufferHubEventFd();
+
+    /**
+     * Returns whether this BufferHubEventFd holds a valid event_fd.
+     */
+    bool isValid() const { return get() >= 0; }
+
+    /**
+     * Returns the fd number of the BufferHubEventFd object. Note that there is no ownership
+     * transfer.
+     */
+    int get() const { return mFd.get(); }
+
+    /**
+     * Signals the eventfd.
+     */
+    status_t signal() const;
+
+    /**
+     * Clears the signal from this eventfd if it is signaled.
+     */
+    status_t clear() const;
+
+private:
+    base::unique_fd mFd;
+};
+
+} // namespace android
+
+#endif // ANDROID_BUFFER_HUB_EVENT_FD_H_
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 0bec0b7..e9da087 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -120,7 +120,7 @@
         right = rb.x;
         bottom = rb.y;
     }
-    
+
     // the following 4 functions return the 4 corners of the rect as Point
     Point leftTop() const {
         return Point(left, top);
@@ -175,6 +175,11 @@
     Rect& offsetTo(int32_t x, int32_t y);
     Rect& offsetBy(int32_t x, int32_t y);
 
+    /**
+     * Insets the rectangle on all sides specified by the insets.
+     */
+    Rect& inset(int32_t _left, int32_t _top, int32_t _right, int32_t _bottom);
+
     bool intersect(const Rect& with, Rect* result) const;
 
     // Create a new Rect by transforming this one using a graphics HAL
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 00f30a6..fcc6d37 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -51,16 +51,23 @@
         "libdvr_headers",
         "libnativewindow_headers",
     ],
+    static_libs: [
+        "libgmock",
+    ],
     shared_libs: [
         "android.frameworks.bufferhub@1.0",
         "libcutils",
         "libhidlbase",
         "libhwbinder",
+        "liblog",
         "libpdx_default_transport",
         "libui",
         "libutils"
     ],
-    srcs: ["BufferHubBuffer_test.cpp"],
+    srcs: [
+        "BufferHubBuffer_test.cpp",
+        "BufferHubEventFd_test.cpp",
+    ],
     cflags: ["-Wall", "-Werror"],
 }
 
diff --git a/libs/ui/tests/BufferHubBuffer_test.cpp b/libs/ui/tests/BufferHubBuffer_test.cpp
index 7c85e66..6c7d06b 100644
--- a/libs/ui/tests/BufferHubBuffer_test.cpp
+++ b/libs/ui/tests/BufferHubBuffer_test.cpp
@@ -127,7 +127,7 @@
     return;
 }
 
-TEST_F(BufferHubBufferTest, AllocateBuffer) {
+TEST_F(BufferHubBufferTest, AllocateAndFreeBuffer) {
     // TODO(b/116681016): directly test on BufferHubBuffer instead of the service.
     sp<IBufferHub> bufferHub = IBufferHub::getService();
     ASSERT_NE(nullptr, bufferHub.get());
@@ -138,11 +138,51 @@
     HardwareBufferDescription desc;
     memcpy(&desc, &aDesc, sizeof(HardwareBufferDescription));
 
-    IBufferHub::allocateBuffer_cb callback = [](const auto& client, const auto& status) {
-        EXPECT_EQ(status, BufferHubStatus::NO_ERROR);
-        EXPECT_NE(nullptr, client.get());
+    sp<IBufferClient> client;
+    BufferHubStatus ret;
+    IBufferHub::allocateBuffer_cb callback = [&](const auto& outClient, const auto& outStatus) {
+        client = outClient;
+        ret = outStatus;
     };
     EXPECT_TRUE(bufferHub->allocateBuffer(desc, kUserMetadataSize, callback).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::NO_ERROR);
+    ASSERT_NE(nullptr, client.get());
+
+    EXPECT_EQ(BufferHubStatus::NO_ERROR, client->close());
+    EXPECT_EQ(BufferHubStatus::CLIENT_CLOSED, client->close());
+}
+
+TEST_F(BufferHubBufferTest, DuplicateFreedBuffer) {
+    // TODO(b/116681016): directly test on BufferHubBuffer instead of the service.
+    sp<IBufferHub> bufferHub = IBufferHub::getService();
+    ASSERT_NE(nullptr, bufferHub.get());
+
+    // Stride is an output, rfu0 and rfu1 are reserved data slot for future use.
+    AHardwareBuffer_Desc aDesc = {kWidth, kHeight,        kLayerCount,  kFormat,
+                                  kUsage, /*stride=*/0UL, /*rfu0=*/0UL, /*rfu1=*/0ULL};
+    HardwareBufferDescription desc;
+    memcpy(&desc, &aDesc, sizeof(HardwareBufferDescription));
+
+    sp<IBufferClient> client;
+    BufferHubStatus ret;
+    IBufferHub::allocateBuffer_cb callback = [&](const auto& outClient, const auto& outStatus) {
+        client = outClient;
+        ret = outStatus;
+    };
+    EXPECT_TRUE(bufferHub->allocateBuffer(desc, kUserMetadataSize, callback).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::NO_ERROR);
+    ASSERT_NE(nullptr, client.get());
+
+    EXPECT_EQ(BufferHubStatus::NO_ERROR, client->close());
+
+    hidl_handle token;
+    IBufferClient::duplicate_cb dup_cb = [&](const auto& outToken, const auto& status) {
+        token = outToken;
+        ret = status;
+    };
+    EXPECT_TRUE(client->duplicate(dup_cb).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::CLIENT_CLOSED);
+    EXPECT_EQ(token.getNativeHandle(), nullptr);
 }
 
 TEST_F(BufferHubBufferTest, DuplicateAndImportBuffer) {
@@ -230,5 +270,50 @@
     native_handle_delete(tokenHandle);
 }
 
+TEST_F(BufferHubBufferTest, ImportFreedBuffer) {
+    // TODO(b/116681016): directly test on BufferHubBuffer instead of the service.
+    sp<IBufferHub> bufferhub = IBufferHub::getService();
+    ASSERT_NE(nullptr, bufferhub.get());
+
+    // Stride is an output, rfu0 and rfu1 are reserved data slot for future use.
+    AHardwareBuffer_Desc aDesc = {kWidth, kHeight,        kLayerCount,  kFormat,
+                                  kUsage, /*stride=*/0UL, /*rfu0=*/0UL, /*rfu1=*/0ULL};
+    HardwareBufferDescription desc;
+    memcpy(&desc, &aDesc, sizeof(HardwareBufferDescription));
+
+    sp<IBufferClient> client;
+    BufferHubStatus ret;
+    IBufferHub::allocateBuffer_cb alloc_cb = [&](const auto& outClient, const auto& status) {
+        client = outClient;
+        ret = status;
+    };
+    ASSERT_TRUE(bufferhub->allocateBuffer(desc, kUserMetadataSize, alloc_cb).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::NO_ERROR);
+    ASSERT_NE(nullptr, client.get());
+
+    hidl_handle token;
+    IBufferClient::duplicate_cb dup_cb = [&](const auto& outToken, const auto& status) {
+        token = outToken;
+        ret = status;
+    };
+    ASSERT_TRUE(client->duplicate(dup_cb).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::NO_ERROR);
+    ASSERT_NE(token.getNativeHandle(), nullptr);
+    EXPECT_EQ(token->numInts, 1);
+    EXPECT_EQ(token->numFds, 0);
+
+    // Close the client. Now the token should be invalid.
+    client->close();
+
+    sp<IBufferClient> client2;
+    IBufferHub::importBuffer_cb import_cb = [&](const auto& outClient, const auto& status) {
+        client2 = outClient;
+        ret = status;
+    };
+    EXPECT_TRUE(bufferhub->importBuffer(token, import_cb).isOk());
+    EXPECT_EQ(ret, BufferHubStatus::INVALID_TOKEN);
+    EXPECT_EQ(nullptr, client2.get());
+}
+
 } // namespace
 } // namespace android
diff --git a/libs/ui/tests/BufferHubEventFd_test.cpp b/libs/ui/tests/BufferHubEventFd_test.cpp
new file mode 100644
index 0000000..92fb33f
--- /dev/null
+++ b/libs/ui/tests/BufferHubEventFd_test.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BufferHubEventFdTest"
+
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include <array>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <ui/BufferHubEventFd.h>
+
+namespace android {
+
+namespace {
+
+const int kTimeout = 100;
+const std::chrono::milliseconds kTimeoutMs(kTimeout);
+
+using ::testing::Contains;
+using BufferHubEventFdTest = ::testing::Test;
+
+} // namespace
+
+TEST_F(BufferHubEventFdTest, EventFd_testSingleEpollFd) {
+    BufferHubEventFd eventFd;
+    ASSERT_TRUE(eventFd.isValid());
+
+    base::unique_fd epollFd(epoll_create(64));
+    epoll_event e = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 0}};
+
+    ASSERT_GE(epollFd.get(), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+
+    std::array<epoll_event, 1> events;
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+
+    eventFd.signal();
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 1);
+
+    // The epoll fd is edge triggered, so it only responds to the eventFd once.
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testClear) {
+    BufferHubEventFd eventFd;
+    ASSERT_TRUE(eventFd.isValid());
+
+    base::unique_fd epollFd(epoll_create(64));
+    epoll_event e = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 0}};
+
+    ASSERT_GE(epollFd.get(), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+
+    eventFd.signal();
+    eventFd.clear();
+
+    std::array<epoll_event, 1> events;
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testDupEventFd) {
+    BufferHubEventFd eventFd;
+    ASSERT_TRUE(eventFd.isValid());
+
+    base::unique_fd epollFd(epoll_create(64));
+    epoll_event e = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 0}};
+
+    ASSERT_GE(epollFd.get(), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+
+    // Technically, the dupliated eventFd and the original eventFd are pointing
+    // to the same kernel object. This test signals the duplicated eventFd but epolls the origianl
+    // eventFd.
+    base::unique_fd dupedEventFd(dup(eventFd.get()));
+    ASSERT_GE(dupedEventFd.get(), 0);
+
+    std::array<epoll_event, 1> events;
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+
+    eventfd_write(dupedEventFd.get(), 1);
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 1);
+
+    // The epoll fd is edge triggered, so it only responds to the eventFd once.
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+
+    eventfd_write(dupedEventFd.get(), 1);
+
+    eventfd_t value;
+    eventfd_read(dupedEventFd.get(), &value);
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testTwoEpollFds) {
+    BufferHubEventFd eventFd;
+    ASSERT_TRUE(eventFd.isValid());
+
+    base::unique_fd epollFd1(epoll_create(64));
+    base::unique_fd epollFd2(epoll_create(64));
+    epoll_event e = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 0}};
+
+    ASSERT_GE(epollFd1.get(), 0);
+    ASSERT_GE(epollFd2.get(), 0);
+
+    // Register the same eventFd to two EpollFds.
+    ASSERT_EQ(epoll_ctl(epollFd1.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+    ASSERT_EQ(epoll_ctl(epollFd2.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+
+    std::array<epoll_event, 1> events;
+    EXPECT_EQ(epoll_wait(epollFd1.get(), events.data(), events.size(), 0), 0);
+    EXPECT_EQ(epoll_wait(epollFd2.get(), events.data(), events.size(), 0), 0);
+
+    eventFd.signal();
+    EXPECT_EQ(epoll_wait(epollFd1.get(), events.data(), events.size(), 0), 1);
+    EXPECT_EQ(epoll_wait(epollFd2.get(), events.data(), events.size(), 0), 1);
+
+    // The epoll fd is edge triggered, so it only responds to the eventFd once.
+    EXPECT_EQ(epoll_wait(epollFd1.get(), events.data(), events.size(), 0), 0);
+    EXPECT_EQ(epoll_wait(epollFd2.get(), events.data(), events.size(), 0), 0);
+
+    eventFd.signal();
+    EXPECT_EQ(epoll_wait(epollFd1.get(), events.data(), events.size(), 0), 1);
+
+    eventFd.clear();
+    EXPECT_EQ(epoll_wait(epollFd1.get(), events.data(), events.size(), 0), 0);
+    EXPECT_EQ(epoll_wait(epollFd2.get(), events.data(), events.size(), 0), 0);
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testTwoEventFds) {
+    BufferHubEventFd eventFd1;
+    BufferHubEventFd eventFd2;
+
+    ASSERT_TRUE(eventFd1.isValid());
+    ASSERT_TRUE(eventFd2.isValid());
+
+    base::unique_fd epollFd(epoll_create(64));
+    epoll_event e1 = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 1}};
+    epoll_event e2 = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 2}};
+
+    ASSERT_GE(epollFd.get(), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd1.get(), &e1), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd2.get(), &e2), 0);
+
+    std::array<epoll_event, 2> events;
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+
+    // Signal one by one.
+    eventFd1.signal();
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 1);
+    EXPECT_EQ(events[0].data.u32, e1.data.u32);
+
+    eventFd2.signal();
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 1);
+    EXPECT_EQ(events[0].data.u32, e2.data.u32);
+
+    // Signal both.
+    eventFd1.signal();
+    eventFd2.signal();
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 2);
+
+    uint32_t u32s[] = {events[0].data.u32, events[1].data.u32};
+    EXPECT_THAT(u32s, Contains(e1.data.u32));
+    EXPECT_THAT(u32s, Contains(e2.data.u32));
+
+    // The epoll fd is edge triggered, so it only responds to the eventFd once.
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 0);
+
+    eventFd1.signal();
+    eventFd2.signal();
+    eventFd2.clear();
+    EXPECT_EQ(epoll_wait(epollFd.get(), events.data(), events.size(), 0), 1);
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testPollingThreadWithTwoEventFds) {
+    BufferHubEventFd eventFd1;
+    BufferHubEventFd eventFd2;
+
+    ASSERT_TRUE(eventFd1.isValid());
+    ASSERT_TRUE(eventFd2.isValid());
+
+    base::unique_fd epollFd(epoll_create(64));
+    epoll_event e1 = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 1}};
+    epoll_event e2 = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 2}};
+
+    ASSERT_GE(epollFd.get(), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd1.get(), &e1), 0);
+    ASSERT_EQ(epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd2.get(), &e2), 0);
+
+    int countEvent1 = 0;
+    int countEvent2 = 0;
+    std::atomic<bool> stop{false};
+    std::mutex mx;
+    std::condition_variable cv;
+
+    std::thread pollingThread([&] {
+        std::array<epoll_event, 2> events;
+        while (true) {
+            if (stop.load()) {
+                break;
+            }
+            int ret = epoll_wait(epollFd.get(), events.data(), events.size(), kTimeout);
+            ALOGE_IF(ret < 0 && errno != ETIMEDOUT, "Epoll failed.");
+
+            std::lock_guard<std::mutex> lock(mx);
+            for (int i = 0; i < ret; i++) {
+                if (events[i].data.u32 == e1.data.u32) {
+                    countEvent1++;
+                    cv.notify_one();
+                } else if (events[i].data.u32 == e2.data.u32) {
+                    countEvent2++;
+                    cv.notify_one();
+                }
+            }
+        }
+    });
+
+    {
+        std::unique_lock<std::mutex> lock(mx);
+
+        eventFd1.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEvent1 == 1; }));
+
+        eventFd1.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEvent1 == 2; }));
+
+        eventFd2.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEvent2 == 1; }));
+
+        eventFd1.clear();
+        eventFd2.clear();
+        EXPECT_EQ(countEvent1, 2);
+        EXPECT_EQ(countEvent2, 1);
+
+        eventFd1.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEvent1 == 3; }));
+
+        eventFd2.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEvent2 == 2; }));
+    }
+
+    stop.store(true);
+    pollingThread.join();
+}
+
+TEST_F(BufferHubEventFdTest, EventFd_testTwoPollingThreads) {
+    BufferHubEventFd eventFd;
+    ASSERT_TRUE(eventFd.isValid());
+
+    base::unique_fd epollFd1(epoll_create(64));
+    base::unique_fd epollFd2(epoll_create(64));
+    epoll_event e = {.events = EPOLLIN | EPOLLET, .data = {.u32 = 0}};
+
+    ASSERT_GE(epollFd1.get(), 0);
+    ASSERT_GE(epollFd2.get(), 0);
+
+    // Register the same eventFd to two EpollFds.
+    ASSERT_EQ(epoll_ctl(epollFd1.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+    ASSERT_EQ(epoll_ctl(epollFd2.get(), EPOLL_CTL_ADD, eventFd.get(), &e), 0);
+
+    int countEpoll1 = 0;
+    int countEpoll2 = 0;
+    std::atomic<bool> stop{false};
+    std::mutex mx;
+    std::condition_variable cv;
+
+    std::thread pollingThread1([&] {
+        std::array<epoll_event, 1> events;
+        while (!stop.load()) {
+            int ret = epoll_wait(epollFd1.get(), events.data(), events.size(), kTimeout);
+            ALOGE_IF(ret < 0 && errno != ETIMEDOUT, "Epoll failed.");
+
+            if (ret > 0) {
+                std::lock_guard<std::mutex> lock(mx);
+                countEpoll1++;
+                cv.notify_one();
+            }
+        }
+    });
+
+    std::thread pollingThread2([&] {
+        std::array<epoll_event, 1> events;
+        while (!stop.load()) {
+            int ret = epoll_wait(epollFd2.get(), events.data(), events.size(), kTimeout);
+            ALOGE_IF(ret < 0 && errno != ETIMEDOUT, "Epoll failed.");
+
+            if (ret > 0) {
+                std::lock_guard<std::mutex> lock(mx);
+                countEpoll2++;
+                cv.notify_one();
+            }
+        }
+    });
+
+    {
+        std::unique_lock<std::mutex> lock(mx);
+
+        eventFd.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll1 == 1; }));
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll2 == 1; }));
+
+        eventFd.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll1 == 2; }));
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll2 == 2; }));
+
+        eventFd.clear();
+        EXPECT_EQ(countEpoll1, 2);
+        EXPECT_EQ(countEpoll2, 2);
+
+        eventFd.signal();
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll1 == 3; }));
+        EXPECT_TRUE(cv.wait_for(lock, kTimeoutMs, [&] { return countEpoll2 == 3; }));
+    }
+
+    stop.store(true);
+    pollingThread1.join();
+    pollingThread2.join();
+}
+
+} // namespace android
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 43ac79e..fa92830 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -22,14 +22,12 @@
     "buffer_hub_base.cpp",
     "buffer_hub_rpc.cpp",
     "consumer_buffer.cpp",
-    "buffer_client_impl.cpp",
     "ion_buffer.cpp",
     "producer_buffer.cpp",
 ]
 
 sharedLibraries = [
     "libbase",
-    "libbinder",
     "libcutils",
     "libhardware",
     "liblog",
diff --git a/libs/vr/libbufferhub/buffer_client_impl.cpp b/libs/vr/libbufferhub/buffer_client_impl.cpp
deleted file mode 100644
index efa9c28..0000000
--- a/libs/vr/libbufferhub/buffer_client_impl.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#include <log/log.h>
-#include <private/dvr/IBufferClient.h>
-
-namespace android {
-namespace dvr {
-
-class BpBufferClient : public BpInterface<IBufferClient> {
- public:
-  explicit BpBufferClient(const sp<IBinder>& impl)
-      : BpInterface<IBufferClient>(impl) {}
-
-  bool isValid() override;
-
-  status_t duplicate(uint64_t* outToken) override;
-};
-
-IMPLEMENT_META_INTERFACE(BufferClient, "android.dvr.IBufferClient");
-
-// Transaction code
-enum {
-  IS_VALID = IBinder::FIRST_CALL_TRANSACTION,
-  DUPLICATE,
-};
-
-bool BpBufferClient::isValid() {
-  Parcel data, reply;
-  status_t ret =
-      data.writeInterfaceToken(IBufferClient::getInterfaceDescriptor());
-  if (ret != OK) {
-    ALOGE("BpBufferClient::isValid: failed to write into parcel; errno=%d",
-          ret);
-    return false;
-  }
-
-  ret = remote()->transact(IS_VALID, data, &reply);
-  if (ret == OK) {
-    return reply.readBool();
-  } else {
-    ALOGE("BpBufferClient::isValid: failed to transact; errno=%d", ret);
-    return false;
-  }
-}
-
-status_t BpBufferClient::duplicate(uint64_t* outToken) {
-  Parcel data, reply;
-  status_t ret =
-      data.writeInterfaceToken(IBufferClient::getInterfaceDescriptor());
-  if (ret != OK) {
-    ALOGE("BpBufferClient::duplicate: failed to write into parcel; errno=%d",
-          ret);
-    return ret;
-  }
-
-  ret = remote()->transact(DUPLICATE, data, &reply);
-  if (ret == OK) {
-    *outToken = reply.readUint64();
-    return OK;
-  } else {
-    ALOGE("BpBufferClient::duplicate: failed to transact; errno=%d", ret);
-    return ret;
-  }
-}
-
-status_t BnBufferClient::onTransact(uint32_t code, const Parcel& data,
-                                    Parcel* reply, uint32_t flags) {
-  switch (code) {
-    case IS_VALID: {
-      CHECK_INTERFACE(IBufferClient, data, reply);
-      return reply->writeBool(isValid());
-    }
-    case DUPLICATE: {
-      CHECK_INTERFACE(IBufferClient, data, reply);
-      uint64_t token = 0;
-      status_t ret = duplicate(&token);
-      if (ret != OK) {
-        return ret;
-      }
-      return reply->writeUint64(token);
-    }
-    default:
-      // Should not reach except binder defined transactions such as dumpsys
-      return BBinder::onTransact(code, data, reply, flags);
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
\ No newline at end of file
diff --git a/libs/vr/libbufferhub/buffer_hub-test.cpp b/libs/vr/libbufferhub/buffer_hub-test.cpp
index 2d9a42b..9bcfaa1 100644
--- a/libs/vr/libbufferhub/buffer_hub-test.cpp
+++ b/libs/vr/libbufferhub/buffer_hub-test.cpp
@@ -212,9 +212,8 @@
   LocalHandle fence;
   EXPECT_EQ(0, p->GainAsync());
 
-  // Acquire and gain in gained state should fail.
+  // Acquire in gained state should fail.
   EXPECT_EQ(-EBUSY, c->Acquire(&fence));
-  EXPECT_EQ(-EALREADY, p->Gain(&fence));
 
   // Post in gained state should succeed.
   EXPECT_EQ(0, p->Post(LocalHandle()));
@@ -242,9 +241,8 @@
   // Gain in released state should succeed.
   EXPECT_EQ(0, p->Gain(&fence));
 
-  // Acquire and gain in gained state should fail.
+  // Acquire in gained state should fail.
   EXPECT_EQ(-EBUSY, c->Acquire(&fence));
-  EXPECT_EQ(-EALREADY, p->Gain(&fence));
 }
 
 TEST_F(LibBufferHubTest, TestAsyncStateTransitions) {
@@ -259,10 +257,9 @@
   LocalHandle invalid_fence;
   EXPECT_EQ(0, p->GainAsync());
 
-  // Acquire and gain in gained state should fail.
+  // Acquire in gained state should fail.
   EXPECT_EQ(-EBUSY, c->AcquireAsync(&metadata, &invalid_fence));
   EXPECT_FALSE(invalid_fence.IsValid());
-  EXPECT_EQ(-EALREADY, p->GainAsync(&metadata, &invalid_fence));
   EXPECT_FALSE(invalid_fence.IsValid());
 
   // Post in gained state should succeed.
@@ -309,8 +306,15 @@
   // Acquire and gain in gained state should fail.
   EXPECT_EQ(-EBUSY, c->AcquireAsync(&metadata, &invalid_fence));
   EXPECT_FALSE(invalid_fence.IsValid());
-  EXPECT_EQ(-EALREADY, p->GainAsync(&metadata, &invalid_fence));
-  EXPECT_FALSE(invalid_fence.IsValid());
+}
+
+TEST_F(LibBufferHubTest, TestGainTwiceByTheSameProducer) {
+  std::unique_ptr<ProducerBuffer> p = ProducerBuffer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(uint64_t));
+  ASSERT_TRUE(p.get() != nullptr);
+
+  ASSERT_EQ(0, p->GainAsync());
+  ASSERT_EQ(0, p->GainAsync());
 }
 
 TEST_F(LibBufferHubTest, TestGainPostedBuffer) {
diff --git a/libs/vr/libbufferhub/include/private/dvr/IBufferClient.h b/libs/vr/libbufferhub/include/private/dvr/IBufferClient.h
deleted file mode 100644
index 31bf79d..0000000
--- a/libs/vr/libbufferhub/include/private/dvr/IBufferClient.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef ANDROID_DVR_IBUFFERCLIENT_H
-#define ANDROID_DVR_IBUFFERCLIENT_H
-
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-
-namespace android {
-namespace dvr {
-
-// Interface for acessing BufferHubBuffer remotely.
-class IBufferClient : public IInterface {
- public:
-  DECLARE_META_INTERFACE(BufferClient);
-
-  // Checks if the buffer node is valid.
-  virtual bool isValid() = 0;
-
-  // Duplicates the client. Token_out will be set to a new token when succeed,
-  // and not changed when failed.
-  virtual status_t duplicate(uint64_t* outToken) = 0;
-};
-
-// BnInterface for IBufferClient. Should only be created in bufferhub service.
-class BnBufferClient : public BnInterface<IBufferClient> {
- public:
-  virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                              uint32_t flags = 0);
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_IBUFFERCLIENT_H
\ No newline at end of file
diff --git a/libs/vr/libbufferhub/producer_buffer.cpp b/libs/vr/libbufferhub/producer_buffer.cpp
index 1bfdc8f..de03fad 100644
--- a/libs/vr/libbufferhub/producer_buffer.cpp
+++ b/libs/vr/libbufferhub/producer_buffer.cpp
@@ -171,10 +171,11 @@
 
   if (BufferHubDefs::IsClientGained(current_buffer_state,
                                     client_state_mask())) {
-    ALOGI("%s: already gained id=%d.", __FUNCTION__, id());
-    return -EALREADY;
+    ALOGV("%s: already gained id=%d.", __FUNCTION__, id());
+    return 0;
   }
   if (BufferHubDefs::AnyClientAcquired(current_buffer_state) ||
+      BufferHubDefs::AnyClientGained(current_buffer_state) ||
       (BufferHubDefs::AnyClientPosted(current_buffer_state) &&
        !gain_posted_buffer)) {
     ALOGE("%s: not released id=%d state=%" PRIx64 ".", __FUNCTION__, id(),
@@ -196,6 +197,7 @@
         __FUNCTION__, current_buffer_state, updated_buffer_state);
 
     if (BufferHubDefs::AnyClientAcquired(current_buffer_state) ||
+        BufferHubDefs::AnyClientGained(current_buffer_state) ||
         (BufferHubDefs::AnyClientPosted(current_buffer_state) &&
          !gain_posted_buffer)) {
       ALOGE(
diff --git a/services/bufferhub/Android.bp b/services/bufferhub/Android.bp
index b747dbc..f9aaa20 100644
--- a/services/bufferhub/Android.bp
+++ b/services/bufferhub/Android.bp
@@ -24,9 +24,9 @@
     ],
     srcs: [
         "BufferClient.cpp",
+        "BufferHubIdGenerator.cpp",
         "BufferHubService.cpp",
         "BufferNode.cpp",
-        "UniqueIdGenerator.cpp",
     ],
     header_libs: [
         "libbufferhub_headers",
diff --git a/services/bufferhub/BufferClient.cpp b/services/bufferhub/BufferClient.cpp
index 37fd75f..7459517 100644
--- a/services/bufferhub/BufferClient.cpp
+++ b/services/bufferhub/BufferClient.cpp
@@ -39,7 +39,29 @@
     return new BufferClient(service, node);
 }
 
+BufferClient::~BufferClient() {
+    close();
+}
+
+Return<BufferHubStatus> BufferClient::close() {
+    std::lock_guard<std::mutex> lock(mClosedMutex);
+    if (mClosed) {
+        return BufferHubStatus::CLIENT_CLOSED;
+    }
+
+    getService()->onClientClosed(this);
+    mBufferNode.reset();
+    mClosed = true;
+    return BufferHubStatus::NO_ERROR;
+}
+
 Return<void> BufferClient::duplicate(duplicate_cb _hidl_cb) {
+    std::lock_guard<std::mutex> lock(mClosedMutex);
+    if (mClosed) {
+        _hidl_cb(/*token=*/hidl_handle(), /*status=*/BufferHubStatus::CLIENT_CLOSED);
+        return Void();
+    }
+
     if (!mBufferNode) {
         // Should never happen
         ALOGE("%s: node is missing.", __FUNCTION__);
@@ -47,15 +69,19 @@
         return Void();
     }
 
+    const hidl_handle token = getService()->registerToken(this);
+    _hidl_cb(/*token=*/token, /*status=*/BufferHubStatus::NO_ERROR);
+    return Void();
+}
+
+sp<BufferHubService> BufferClient::getService() {
     sp<BufferHubService> service = mService.promote();
     if (service == nullptr) {
         // Should never happen. Kill the process.
         LOG_FATAL("%s: service died.", __FUNCTION__);
     }
 
-    const hidl_handle token = service->registerToken(this);
-    _hidl_cb(/*token=*/token, /*status=*/BufferHubStatus::NO_ERROR);
-    return Void();
+    return service;
 }
 
 } // namespace implementation
diff --git a/services/bufferhub/UniqueIdGenerator.cpp b/services/bufferhub/BufferHubIdGenerator.cpp
similarity index 81%
rename from services/bufferhub/UniqueIdGenerator.cpp
rename to services/bufferhub/BufferHubIdGenerator.cpp
index 362a026..6444a03 100644
--- a/services/bufferhub/UniqueIdGenerator.cpp
+++ b/services/bufferhub/BufferHubIdGenerator.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <bufferhub/UniqueIdGenerator.h>
+#include <bufferhub/BufferHubIdGenerator.h>
 
 namespace android {
 namespace frameworks {
@@ -22,9 +22,15 @@
 namespace V1_0 {
 namespace implementation {
 
-constexpr uint32_t UniqueIdGenerator::kInvalidId;
+constexpr uint32_t BufferHubIdGenerator::kInvalidId;
 
-uint32_t UniqueIdGenerator::getId() {
+BufferHubIdGenerator& BufferHubIdGenerator::getInstance() {
+    static BufferHubIdGenerator generator;
+
+    return generator;
+}
+
+uint32_t BufferHubIdGenerator::getId() {
     std::lock_guard<std::mutex> lock(mIdsInUseMutex);
 
     do {
@@ -37,7 +43,7 @@
     return mLastId;
 }
 
-bool UniqueIdGenerator::freeId(uint32_t id) {
+bool BufferHubIdGenerator::freeId(uint32_t id) {
     std::lock_guard<std::mutex> lock(mIdsInUseMutex);
     auto iter = mIdsInUse.find(id);
     if (iter != mIdsInUse.end()) {
diff --git a/services/bufferhub/BufferHubService.cpp b/services/bufferhub/BufferHubService.cpp
index 6f97f0d..b0869fe 100644
--- a/services/bufferhub/BufferHubService.cpp
+++ b/services/bufferhub/BufferHubService.cpp
@@ -35,7 +35,8 @@
 
     std::shared_ptr<BufferNode> node =
             std::make_shared<BufferNode>(desc.width, desc.height, desc.layers, desc.format,
-                                         desc.usage, userMetadataSize, nodeIdGenerator.getId());
+                                         desc.usage, userMetadataSize,
+                                         BufferHubIdGenerator::getInstance().getId());
     if (node == nullptr || !node->IsValid()) {
         ALOGE("%s: creating BufferNode failed.", __FUNCTION__);
         _hidl_cb(/*bufferClient=*/nullptr, /*status=*/BufferHubStatus::ALLOCATION_FAILED);
@@ -44,8 +45,8 @@
 
     sp<BufferClient> client = BufferClient::create(this, node);
     // Add it to list for bookkeeping and dumpsys.
-    std::lock_guard<std::mutex> lock(mClientListMutex);
-    mClientList.push_back(client);
+    std::lock_guard<std::mutex> lock(mClientSetMutex);
+    mClientSet.emplace(client);
 
     _hidl_cb(/*bufferClient=*/client, /*status=*/BufferHubStatus::NO_ERROR);
     return Void();
@@ -86,8 +87,8 @@
 
     sp<BufferClient> client = new BufferClient(*originClient);
 
-    std::lock_guard<std::mutex> lock(mClientListMutex);
-    mClientList.push_back(client);
+    std::lock_guard<std::mutex> lock(mClientSetMutex);
+    mClientSet.emplace(client);
     _hidl_cb(/*bufferClient=*/client, /*status=*/BufferHubStatus::NO_ERROR);
     return Void();
 }
@@ -111,6 +112,30 @@
     return returnToken;
 }
 
+void BufferHubService::onClientClosed(const BufferClient* client) {
+    removeTokenByClient(client);
+
+    std::lock_guard<std::mutex> lock(mClientSetMutex);
+    auto iter = std::find(mClientSet.begin(), mClientSet.end(), client);
+    if (iter != mClientSet.end()) {
+        mClientSet.erase(iter);
+    }
+}
+
+void BufferHubService::removeTokenByClient(const BufferClient* client) {
+    std::lock_guard<std::mutex> lock(mTokenMapMutex);
+    auto iter = mTokenMap.begin();
+    while (iter != mTokenMap.end()) {
+        if (iter->second == client) {
+            auto oldIter = iter;
+            ++iter;
+            mTokenMap.erase(oldIter);
+        } else {
+            ++iter;
+        }
+    }
+}
+
 } // namespace implementation
 } // namespace V1_0
 } // namespace bufferhub
diff --git a/services/bufferhub/BufferNode.cpp b/services/bufferhub/BufferNode.cpp
index 715d0a1..ec84849 100644
--- a/services/bufferhub/BufferNode.cpp
+++ b/services/bufferhub/BufferNode.cpp
@@ -66,8 +66,8 @@
     }
 
     // Free the id, if valid
-    if (id() != UniqueIdGenerator::kInvalidId) {
-        if (nodeIdGenerator.freeId(id())) {
+    if (id() != BufferHubIdGenerator::kInvalidId) {
+        if (BufferHubIdGenerator::getInstance().freeId(id())) {
             ALOGI("%s: id #%u is freed.", __FUNCTION__, id());
         } else {
             ALOGE("%s: Cannot free nonexistent id #%u", __FUNCTION__, id());
diff --git a/services/bufferhub/include/bufferhub/BufferClient.h b/services/bufferhub/include/bufferhub/BufferClient.h
index 769ec86..7f5d3a6 100644
--- a/services/bufferhub/include/bufferhub/BufferClient.h
+++ b/services/bufferhub/include/bufferhub/BufferClient.h
@@ -44,14 +44,22 @@
     // Creates a BufferClient from an existing BufferClient. Will share the same BufferNode.
     explicit BufferClient(const BufferClient& other)
           : mService(other.mService), mBufferNode(other.mBufferNode) {}
+    ~BufferClient();
 
+    Return<BufferHubStatus> close() override;
     Return<void> duplicate(duplicate_cb _hidl_cb) override;
 
 private:
     BufferClient(wp<BufferHubService> service, const std::shared_ptr<BufferNode>& node)
           : mService(service), mBufferNode(node) {}
 
+    sp<BufferHubService> getService();
+
     wp<BufferHubService> mService;
+
+    std::mutex mClosedMutex;
+    bool mClosed GUARDED_BY(mClosedMutex) = false;
+
     std::shared_ptr<BufferNode> mBufferNode;
 };
 
diff --git a/services/bufferhub/include/bufferhub/UniqueIdGenerator.h b/services/bufferhub/include/bufferhub/BufferHubIdGenerator.h
similarity index 89%
rename from services/bufferhub/include/bufferhub/UniqueIdGenerator.h
rename to services/bufferhub/include/bufferhub/BufferHubIdGenerator.h
index d2e702f..c5b2cde 100644
--- a/services/bufferhub/include/bufferhub/UniqueIdGenerator.h
+++ b/services/bufferhub/include/bufferhub/BufferHubIdGenerator.h
@@ -29,11 +29,14 @@
 namespace implementation {
 
 // A thread-safe incremental uint32_t id generator.
-class UniqueIdGenerator {
+class BufferHubIdGenerator {
 public:
     // 0 is considered invalid
     static constexpr uint32_t kInvalidId = 0UL;
 
+    // Get the singleton instance of this class
+    static BufferHubIdGenerator& getInstance();
+
     // Gets next available id. If next id is greater than std::numeric_limits<uint32_t>::max() (2 ^
     // 32 - 1), it will try to get an id start from 1 again.
     uint32_t getId();
@@ -42,6 +45,9 @@
     bool freeId(uint32_t id);
 
 private:
+    BufferHubIdGenerator() = default;
+    ~BufferHubIdGenerator() = default;
+
     std::mutex mIdsInUseMutex;
     // Start from kInvalidID to avoid generating it.
     uint32_t mLastId = kInvalidId;
diff --git a/services/bufferhub/include/bufferhub/BufferHubService.h b/services/bufferhub/include/bufferhub/BufferHubService.h
index 6535659..f2c8ef8 100644
--- a/services/bufferhub/include/bufferhub/BufferHubService.h
+++ b/services/bufferhub/include/bufferhub/BufferHubService.h
@@ -22,7 +22,7 @@
 
 #include <android/frameworks/bufferhub/1.0/IBufferHub.h>
 #include <bufferhub/BufferClient.h>
-#include <bufferhub/UniqueIdGenerator.h>
+#include <bufferhub/BufferHubIdGenerator.h>
 #include <utils/Mutex.h>
 
 namespace android {
@@ -35,8 +35,6 @@
 using hardware::Return;
 using hardware::graphics::common::V1_2::HardwareBufferDescription;
 
-static UniqueIdGenerator nodeIdGenerator;
-
 class BufferHubService : public IBufferHub {
 public:
     Return<void> allocateBuffer(const HardwareBufferDescription& description,
@@ -48,10 +46,15 @@
     // Internal help function for IBufferClient::duplicate.
     hidl_handle registerToken(const wp<BufferClient>& client);
 
+    void onClientClosed(const BufferClient* client);
+
 private:
+    // Helper function to remove all the token belongs to a specific client.
+    void removeTokenByClient(const BufferClient* client);
+
     // List of active BufferClient for bookkeeping.
-    std::mutex mClientListMutex;
-    std::vector<wp<BufferClient>> mClientList GUARDED_BY(mClientListMutex);
+    std::mutex mClientSetMutex;
+    std::set<wp<BufferClient>> mClientSet GUARDED_BY(mClientSetMutex);
 
     // TODO(b/118180214): use a more secure implementation
     std::mt19937 mTokenEngine;
diff --git a/services/bufferhub/include/bufferhub/BufferNode.h b/services/bufferhub/include/bufferhub/BufferNode.h
index c490e7c..94ef505 100644
--- a/services/bufferhub/include/bufferhub/BufferNode.h
+++ b/services/bufferhub/include/bufferhub/BufferNode.h
@@ -2,7 +2,7 @@
 #define ANDROID_FRAMEWORKS_BUFFERHUB_V1_0_BUFFER_NODE_H_
 
 #include <android/hardware_buffer.h>
-#include <bufferhub/UniqueIdGenerator.h>
+#include <bufferhub/BufferHubIdGenerator.h>
 #include <ui/BufferHubMetadata.h>
 
 namespace android {
@@ -16,7 +16,7 @@
     // Allocates a new BufferNode.
     BufferNode(uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
                uint64_t usage, size_t user_metadata_size,
-               uint32_t id = UniqueIdGenerator::kInvalidId);
+               uint32_t id = BufferHubIdGenerator::kInvalidId);
 
     ~BufferNode();
 
diff --git a/services/bufferhub/tests/Android.bp b/services/bufferhub/tests/Android.bp
index 8a30ef5..3967886 100644
--- a/services/bufferhub/tests/Android.bp
+++ b/services/bufferhub/tests/Android.bp
@@ -24,10 +24,10 @@
 }
 
 cc_test {
-    name: "UniqueIdGenerator_test",
-    srcs: ["UniqueIdGenerator_test.cpp"],
+    name: "BufferHubIdGenerator_test",
+    srcs: ["BufferHubIdGenerator_test.cpp"],
     cflags: [
-        "-DLOG_TAG=\"UniqueIdGenerator_test\"",
+        "-DLOG_TAG=\"BufferHubIdGenerator_test\"",
         "-DTRACE=0",
         "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
     ],
diff --git a/services/bufferhub/tests/BufferHubIdGenerator_test.cpp b/services/bufferhub/tests/BufferHubIdGenerator_test.cpp
new file mode 100644
index 0000000..4eddfe0
--- /dev/null
+++ b/services/bufferhub/tests/BufferHubIdGenerator_test.cpp
@@ -0,0 +1,45 @@
+#include <bufferhub/BufferHubIdGenerator.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace frameworks {
+namespace bufferhub {
+namespace V1_0 {
+namespace implementation {
+
+namespace {
+
+class BufferHubIdGeneratorTest : public testing::Test {
+protected:
+    BufferHubIdGenerator* mIdGenerator = &BufferHubIdGenerator::getInstance();
+};
+
+TEST_F(BufferHubIdGeneratorTest, TestGenerateAndFreeID) {
+    uint32_t id = mIdGenerator->getId();
+    EXPECT_NE(id, BufferHubIdGenerator::kInvalidId);
+
+    EXPECT_TRUE(mIdGenerator->freeId(id));
+    EXPECT_FALSE(mIdGenerator->freeId(id));
+}
+
+TEST_F(BufferHubIdGeneratorTest, TestGenerateUniqueIncrementalID) {
+    // 10 IDs should not overflow the UniqueIdGenerator to cause a roll back to start, so the
+    // resulting IDs should still keep incresing.
+    const size_t kTestSize = 10U;
+    uint32_t ids[kTestSize];
+    for (int i = 0; i < kTestSize; ++i) {
+        ids[i] = mIdGenerator->getId();
+        EXPECT_NE(ids[i], BufferHubIdGenerator::kInvalidId);
+        if (i >= 1) {
+            EXPECT_GT(ids[i], ids[i - 1]);
+        }
+    }
+}
+
+} // namespace
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace bufferhub
+} // namespace frameworks
+} // namespace android
\ No newline at end of file
diff --git a/services/bufferhub/tests/UniqueIdGenerator_test.cpp b/services/bufferhub/tests/UniqueIdGenerator_test.cpp
deleted file mode 100644
index c4d83e0..0000000
--- a/services/bufferhub/tests/UniqueIdGenerator_test.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include <bufferhub/UniqueIdGenerator.h>
-#include <gtest/gtest.h>
-
-namespace android {
-namespace frameworks {
-namespace bufferhub {
-namespace V1_0 {
-namespace implementation {
-
-namespace {
-
-class UniqueIdGeneratorTest : public testing::Test {
-protected:
-    UniqueIdGenerator mIdGenerator;
-};
-
-TEST_F(UniqueIdGeneratorTest, TestGenerateAndFreeID) {
-    uint32_t id = mIdGenerator.getId();
-    EXPECT_NE(id, UniqueIdGenerator::kInvalidId);
-
-    EXPECT_TRUE(mIdGenerator.freeId(id));
-    EXPECT_FALSE(mIdGenerator.freeId(id));
-}
-
-TEST_F(UniqueIdGeneratorTest, TestGenerateUniqueIncrementalID) {
-    // 10 IDs should not overflow the UniqueIdGenerator to cause a roll back to start, so the
-    // resulting IDs should still keep incresing.
-    const size_t kTestSize = 10U;
-    uint32_t ids[kTestSize];
-    for (int i = 0; i < kTestSize; ++i) {
-        ids[i] = mIdGenerator.getId();
-        EXPECT_NE(ids[i], UniqueIdGenerator::kInvalidId);
-        if (i >= 1) {
-            EXPECT_GT(ids[i], ids[i - 1]);
-        }
-    }
-}
-
-} // namespace
-
-} // namespace implementation
-} // namespace V1_0
-} // namespace bufferhub
-} // namespace frameworks
-} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/InputDispatcher.cpp b/services/inputflinger/InputDispatcher.cpp
index 1dca1b6..885348e 100644
--- a/services/inputflinger/InputDispatcher.cpp
+++ b/services/inputflinger/InputDispatcher.cpp
@@ -3077,22 +3077,44 @@
             // Remove all handles on a display if there are no windows left.
             mWindowHandlesByDisplay.erase(displayId);
         } else {
-            size_t numWindows = inputWindowHandles.size();
+            // Since we compare the pointer of input window handles across window updates, we need
+            // to make sure the handle object for the same window stays unchanged across updates.
+            const Vector<sp<InputWindowHandle>>& oldHandles = mWindowHandlesByDisplay[displayId];
+            std::unordered_map<sp<IBinder>, sp<InputWindowHandle>, IBinderHash> oldHandlesByTokens;
+            for (size_t i = 0; i < oldHandles.size(); i++) {
+                const sp<InputWindowHandle>& handle = oldHandles.itemAt(i);
+                oldHandlesByTokens[handle->getToken()] = handle;
+            }
+
+            const size_t numWindows = inputWindowHandles.size();
+            Vector<sp<InputWindowHandle>> newHandles;
             for (size_t i = 0; i < numWindows; i++) {
-                const sp<InputWindowHandle>& windowHandle = inputWindowHandles.itemAt(i);
-                if (!windowHandle->updateInfo() || getInputChannelLocked(windowHandle->getToken()) == nullptr) {
+                const sp<InputWindowHandle>& handle = inputWindowHandles.itemAt(i);
+                if (!handle->updateInfo() || getInputChannelLocked(handle->getToken()) == nullptr) {
                     ALOGE("Window handle %s has no registered input channel",
-                            windowHandle->getName().c_str());
+                            handle->getName().c_str());
                     continue;
                 }
 
-                if (windowHandle->getInfo()->displayId != displayId) {
+                if (handle->getInfo()->displayId != displayId) {
                     ALOGE("Window %s updated by wrong display %d, should belong to display %d",
-                        windowHandle->getName().c_str(), displayId,
-                        windowHandle->getInfo()->displayId);
+                        handle->getName().c_str(), displayId,
+                        handle->getInfo()->displayId);
                     continue;
                 }
 
+                if (oldHandlesByTokens.find(handle->getToken()) != oldHandlesByTokens.end()) {
+                    const sp<InputWindowHandle> oldHandle =
+                            oldHandlesByTokens.at(handle->getToken());
+                    oldHandle->updateFrom(handle);
+                    newHandles.push_back(oldHandle);
+                } else {
+                    newHandles.push_back(handle);
+                }
+            }
+
+            for (size_t i = 0; i < newHandles.size(); i++) {
+                const sp<InputWindowHandle>& windowHandle = newHandles.itemAt(i);
                 if (windowHandle->getInfo()->hasFocus && windowHandle->getInfo()->visible) {
                     newFocusedWindowHandle = windowHandle;
                 }
@@ -3102,7 +3124,7 @@
             }
 
             // Insert or replace
-            mWindowHandlesByDisplay[displayId] = inputWindowHandles;
+            mWindowHandlesByDisplay[displayId] = newHandles;
         }
 
         if (!foundHoveredWindow) {
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index db945bb..24b0dd7 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -117,6 +117,8 @@
             void onUidGone(uid_t uid, bool disabled);
             void onUidActive(uid_t uid);
             void onUidIdle(uid_t uid, bool disabled);
+            void onUidStateChanged(uid_t uid __unused, int32_t procState __unused,
+                                   int64_t procStateSeq __unused) {}
 
             void addOverrideUid(uid_t uid, bool active);
             void removeOverrideUid(uid_t uid);
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index fd40f6c..9b1c0db 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -24,6 +24,8 @@
 #include "DisplayDevice.h"
 #include "LayerRejecter.h"
 
+#include "TimeStats/TimeStats.h"
+
 #include <renderengine/RenderEngine.h>
 
 #include <gui/BufferItem.h>
@@ -71,7 +73,7 @@
         destroyAllHwcLayersPlusChildren();
     }
 
-    mTimeStats.onDestroy(getSequence());
+    mFlinger->mTimeStats->onDestroy(getSequence());
 }
 
 void BufferLayer::useSurfaceDamage() {
@@ -334,7 +336,7 @@
     mFrameTracker.setDesiredPresentTime(desiredPresentTime);
 
     const int32_t layerID = getSequence();
-    mTimeStats.setDesiredTime(layerID, mCurrentFrameNumber, desiredPresentTime);
+    mFlinger->mTimeStats->setDesiredTime(layerID, mCurrentFrameNumber, desiredPresentTime);
 
     std::shared_ptr<FenceTime> frameReadyFence = getCurrentFenceTime();
     if (frameReadyFence->isValid()) {
@@ -346,13 +348,13 @@
     }
 
     if (presentFence->isValid()) {
-        mTimeStats.setPresentFence(layerID, mCurrentFrameNumber, presentFence);
+        mFlinger->mTimeStats->setPresentFence(layerID, mCurrentFrameNumber, presentFence);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
     } else if (displayId && mFlinger->getHwComposer().isConnected(*displayId)) {
         // The HWC doesn't support present fences, so use the refresh
         // timestamp instead.
         const nsecs_t actualPresentTime = mFlinger->getHwComposer().getRefreshTimestamp(*displayId);
-        mTimeStats.setPresentTime(layerID, mCurrentFrameNumber, actualPresentTime);
+        mFlinger->mTimeStats->setPresentTime(layerID, mCurrentFrameNumber, actualPresentTime);
         mFrameTracker.setActualPresentTime(actualPresentTime);
     }
 
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 162f391..b784d11 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -17,6 +17,8 @@
 #include "BufferQueueLayer.h"
 #include "LayerRejecter.h"
 
+#include "TimeStats/TimeStats.h"
+
 #include <system/window.h>
 
 namespace android {
@@ -246,7 +248,7 @@
         // and return early
         if (queuedBuffer) {
             Mutex::Autolock lock(mQueueItemLock);
-            mTimeStats.removeTimeRecord(layerID, mQueueItems[0].mFrameNumber);
+            mFlinger->mTimeStats->removeTimeRecord(layerID, mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             mQueuedFrames--;
         }
@@ -260,7 +262,7 @@
             Mutex::Autolock lock(mQueueItemLock);
             mQueueItems.clear();
             mQueuedFrames = 0;
-            mTimeStats.clearLayerRecord(layerID);
+            mFlinger->mTimeStats->onDestroy(layerID);
         }
 
         // Once we have hit this state, the shadow queue may no longer
@@ -281,13 +283,14 @@
         // Remove any stale buffers that have been dropped during
         // updateTexImage
         while (mQueueItems[0].mFrameNumber != currentFrameNumber) {
-            mTimeStats.removeTimeRecord(layerID, mQueueItems[0].mFrameNumber);
+            mFlinger->mTimeStats->removeTimeRecord(layerID, mQueueItems[0].mFrameNumber);
             mQueueItems.removeAt(0);
             mQueuedFrames--;
         }
 
-        mTimeStats.setAcquireFence(layerID, currentFrameNumber, mQueueItems[0].mFenceTime);
-        mTimeStats.setLatchTime(layerID, currentFrameNumber, latchTime);
+        mFlinger->mTimeStats->setAcquireFence(layerID, currentFrameNumber,
+                                              mQueueItems[0].mFenceTime);
+        mFlinger->mTimeStats->setLatchTime(layerID, currentFrameNumber, latchTime);
 
         mQueueItems.removeAt(0);
     }
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 5ffd8ad..1f71a44 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -21,6 +21,8 @@
 
 #include "BufferStateLayer.h"
 
+#include "TimeStats/TimeStats.h"
+
 #include <private/gui/SyncFeatures.h>
 #include <renderengine/Image.h>
 
@@ -457,7 +459,7 @@
         ALOGE("[%s] rejecting buffer: "
               "bufferWidth=%d, bufferHeight=%d, front.active.{w=%d, h=%d}",
               mName.string(), bufferWidth, bufferHeight, s.active.w, s.active.h);
-        mTimeStats.removeTimeRecord(layerID, getFrameNumber());
+        mFlinger->mTimeStats->removeTimeRecord(layerID, getFrameNumber());
         return BAD_VALUE;
     }
 
@@ -469,7 +471,7 @@
     if (SyncFeatures::getInstance().useNativeFenceSync() && releaseFence != Fence::NO_FENCE) {
         // TODO(alecmouri): Fail somewhere upstream if the fence is invalid.
         if (!releaseFence->isValid()) {
-            mTimeStats.clearLayerRecord(layerID);
+            mFlinger->mTimeStats->onDestroy(layerID);
             return UNKNOWN_ERROR;
         }
 
@@ -479,7 +481,7 @@
         auto currentStatus = s.acquireFence->getStatus();
         if (currentStatus == Fence::Status::Invalid) {
             ALOGE("Existing fence has invalid state");
-            mTimeStats.clearLayerRecord(layerID);
+            mFlinger->mTimeStats->onDestroy(layerID);
             return BAD_VALUE;
         }
 
@@ -487,7 +489,7 @@
         if (incomingStatus == Fence::Status::Invalid) {
             ALOGE("New fence has invalid state");
             mDrawingState.acquireFence = releaseFence;
-            mTimeStats.clearLayerRecord(layerID);
+            mFlinger->mTimeStats->onDestroy(layerID);
             return BAD_VALUE;
         }
 
@@ -503,7 +505,7 @@
                 // synchronization is broken, the best we can do is hope fences
                 // signal in order so the new fence will act like a union
                 mDrawingState.acquireFence = releaseFence;
-                mTimeStats.clearLayerRecord(layerID);
+                mFlinger->mTimeStats->onDestroy(layerID);
                 return BAD_VALUE;
             }
             mDrawingState.acquireFence = mergedFence;
@@ -526,15 +528,15 @@
         // a GL-composited layer) not at all.
         status_t err = bindTextureImage();
         if (err != NO_ERROR) {
-            mTimeStats.clearLayerRecord(layerID);
+            mFlinger->mTimeStats->onDestroy(layerID);
             return BAD_VALUE;
         }
     }
 
     // TODO(marissaw): properly support mTimeStats
-    mTimeStats.setPostTime(layerID, getFrameNumber(), getName().c_str(), latchTime);
-    mTimeStats.setAcquireFence(layerID, getFrameNumber(), getCurrentFenceTime());
-    mTimeStats.setLatchTime(layerID, getFrameNumber(), latchTime);
+    mFlinger->mTimeStats->setPostTime(layerID, getFrameNumber(), getName().c_str(), latchTime);
+    mFlinger->mTimeStats->setAcquireFence(layerID, getFrameNumber(), getCurrentFenceTime());
+    mFlinger->mTimeStats->setLatchTime(layerID, getFrameNumber(), latchTime);
 
     return NO_ERROR;
 }
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index 5b641a2..3b7ed15 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -1067,6 +1067,18 @@
     return error;
 }
 
+Error Composer::setDisplayContentSamplingEnabled(Display display, bool enabled,
+                                                 uint8_t componentMask, uint64_t maxFrames) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    auto enable = enabled ? V2_3::IComposerClient::DisplayedContentSampling::ENABLE
+                          : V2_3::IComposerClient::DisplayedContentSampling::DISABLE;
+    return mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
+                                                           maxFrames);
+}
+
 CommandReader::~CommandReader()
 {
     resetData();
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 61af348..0db12a1 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -192,6 +192,8 @@
     virtual Error getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
                                                         Dataspace* outDataspace,
                                                         uint8_t* outComponentMask) = 0;
+    virtual Error setDisplayContentSamplingEnabled(Display display, bool enabled,
+                                                   uint8_t componentMask, uint64_t maxFrames) = 0;
     virtual Error getDisplayCapabilities(Display display,
                                          std::vector<DisplayCapability>* outCapabilities) = 0;
 };
@@ -396,6 +398,8 @@
     Error getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
                                                 Dataspace* outDataspace,
                                                 uint8_t* outComponentMask) override;
+    Error setDisplayContentSamplingEnabled(Display display, bool enabled, uint8_t componentMask,
+                                           uint64_t maxFrames) override;
     Error getDisplayCapabilities(Display display,
                                  std::vector<DisplayCapability>* outCapabilities) override;
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 2df2d3b..733a5da 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -555,6 +555,13 @@
     return static_cast<Error>(intError);
 }
 
+Error Display::setDisplayContentSamplingEnabled(bool enabled, uint8_t componentMask,
+                                                uint64_t maxFrames) const {
+    auto intError =
+            mComposer.setDisplayContentSamplingEnabled(mId, enabled, componentMask, maxFrames);
+    return static_cast<Error>(intError);
+}
+
 Error Display::getReleaseFences(
         std::unordered_map<Layer*, sp<Fence>>* outFences) const
 {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 8c0f50c..2d65051 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -240,6 +240,9 @@
     [[clang::warn_unused_result]] Error getDisplayedContentSamplingAttributes(
             android::ui::PixelFormat* outFormat, android::ui::Dataspace* outDataspace,
             uint8_t* outComponentMask) const;
+    [[clang::warn_unused_result]] Error setDisplayContentSamplingEnabled(bool enabled,
+                                                                         uint8_t componentMask,
+                                                                         uint64_t maxFrames) const;
     [[clang::warn_unused_result]] Error getReleaseFences(
             std::unordered_map<Layer*,
                     android::sp<android::Fence>>* outFences) const;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index a752a7d..b27344d 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -756,6 +756,20 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::setDisplayContentSamplingEnabled(DisplayId displayId, bool enabled,
+                                                      uint8_t componentMask, uint64_t maxFrames) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error =
+            mDisplayData[displayId].hwcDisplay->setDisplayContentSamplingEnabled(enabled,
+                                                                                 componentMask,
+                                                                                 maxFrames);
+
+    if (error == HWC2::Error::Unsupported) RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    if (error == HWC2::Error::BadParameter) RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 bool HWComposer::isUsingVrComposer() const {
     return getComposer()->isUsingVrComposer();
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 0375f74..3f1328e 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -131,6 +131,8 @@
     status_t getDisplayedContentSamplingAttributes(DisplayId displayId, ui::PixelFormat* outFormat,
                                                    ui::Dataspace* outDataspace,
                                                    uint8_t* outComponentMask);
+    status_t setDisplayContentSamplingEnabled(DisplayId displayId, bool enabled,
+                                              uint8_t componentMask, uint64_t maxFrames);
 
     // Events handling ---------------------------------------------------------
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index aa53382..f53ffae 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -52,6 +52,7 @@
 #include "SurfaceFlinger.h"
 
 #include "DisplayHardware/HWComposer.h"
+#include "TimeStats/TimeStats.h"
 
 #include <renderengine/RenderEngine.h>
 
@@ -1522,14 +1523,14 @@
 void Layer::onDisconnect() {
     Mutex::Autolock lock(mFrameEventHistoryMutex);
     mFrameEventHistory.onDisconnect();
-    mTimeStats.onDisconnect(getSequence());
+    mFlinger->mTimeStats->onDestroy(getSequence());
 }
 
 void Layer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
                                      FrameEventHistoryDelta* outDelta) {
     if (newTimestamps) {
-        mTimeStats.setPostTime(getSequence(), newTimestamps->frameNumber, getName().c_str(),
-                               newTimestamps->postedTime);
+        mFlinger->mTimeStats->setPostTime(getSequence(), newTimestamps->frameNumber,
+                                          getName().c_str(), newTimestamps->postedTime);
     }
 
     Mutex::Autolock lock(mFrameEventHistoryMutex);
@@ -1685,12 +1686,20 @@
     return true;
 }
 
-const mat4& Layer::getColorTransform() const {
-    return getDrawingState().colorTransform;
+mat4 Layer::getColorTransform() const {
+    mat4 colorTransform = mat4(getDrawingState().colorTransform);
+    if (sp<Layer> parent = mDrawingParent.promote(); parent != nullptr) {
+        colorTransform = parent->getColorTransform() * colorTransform;
+    }
+    return colorTransform;
 }
 
 bool Layer::hasColorTransform() const {
-    return getDrawingState().hasColorTransform;
+    bool hasColorTransform = getDrawingState().hasColorTransform;
+    if (sp<Layer> parent = mDrawingParent.promote(); parent != nullptr) {
+        hasColorTransform = hasColorTransform || parent->hasColorTransform();
+    }
+    return hasColorTransform;
 }
 
 bool Layer::isLegacyDataSpace() const {
@@ -2112,10 +2121,6 @@
 
 InputWindowInfo Layer::fillInputInfo(const Rect& screenBounds) {
     InputWindowInfo info = mDrawingState.inputInfo;
-    info.frameLeft = screenBounds.left + info.surfaceInset;
-    info.frameTop = screenBounds.top + info.surfaceInset;
-    info.frameRight = screenBounds.right - info.surfaceInset;
-    info.frameBottom = screenBounds.bottom - info.surfaceInset;
 
     ui::Transform t = getTransform();
     const float xScale = t.sx();
@@ -2126,9 +2131,26 @@
         info.touchableRegion.scaleSelf(xScale, yScale);
     }
 
-    info.touchableRegion = info.touchableRegion.translate(
-            screenBounds.left,
-            screenBounds.top);
+    // Transform layer size to screen space and inset it by surface insets.
+    Rect layerBounds = getCroppedBufferSize(getDrawingState());
+    layerBounds = t.transform(layerBounds);
+    layerBounds.inset(info.surfaceInset, info.surfaceInset, info.surfaceInset, info.surfaceInset);
+
+    // Intersect with screen bounds to shrink the frame by the surface insets. The surface insets
+    // are not set on the screen bounds directly since the surface inset region may already be
+    // cropped by a parent layer.
+    Rect frame;
+    screenBounds.intersect(layerBounds, &frame);
+
+    info.frameLeft = frame.left;
+    info.frameTop = frame.top;
+    info.frameRight = frame.right;
+    info.frameBottom = frame.bottom;
+
+    // Position the touchable region relative to frame screen location and restrict it to frame
+    // bounds.
+    info.touchableRegion = info.touchableRegion.translate(info.frameLeft, info.frameTop);
+    info.touchableRegion = info.touchableRegion.intersect(frame);
     info.visible = isVisible();
     return info;
 }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 687fc0a..6ea80c7 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -48,7 +48,6 @@
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
 #include "SurfaceFlinger.h"
-#include "TimeStats/TimeStats.h"
 #include "TransactionCompletedThread.h"
 
 #include "DisplayHardware/HWComposer.h"
@@ -290,7 +289,7 @@
     bool attachChildren();
     bool isLayerDetached() const { return mLayerDetached; }
     virtual bool setColorTransform(const mat4& matrix);
-    virtual const mat4& getColorTransform() const;
+    virtual mat4 getColorTransform() const;
     virtual bool hasColorTransform() const;
 
     // Used only to set BufferStateLayer state
@@ -789,8 +788,6 @@
     FenceTimeline mAcquireTimeline;
     FenceTimeline mReleaseTimeline;
 
-    TimeStats& mTimeStats = TimeStats::getInstance();
-
     // main thread
     sp<GraphicBuffer> mActiveBuffer;
     ui::Dataspace mCurrentDataSpace = ui::Dataspace::UNKNOWN;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5b3c477..0bda020 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -95,6 +95,7 @@
 #include "Scheduler/InjectVSyncSource.h"
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/Scheduler.h"
+#include "TimeStats/TimeStats.h"
 
 #include <cutils/compiler.h>
 
@@ -263,6 +264,7 @@
         mDebugInTransaction(0),
         mLastTransactionTime(0),
         mForceFullDamage(false),
+        mTimeStats(factory.createTimeStats()),
         mPrimaryHWVsyncEnabled(false),
         mHWVsyncAvailable(false),
         mRefreshStartTime(0),
@@ -1121,8 +1123,7 @@
     if (!outFormat || !outDataspace || !outComponentMask) {
         return BAD_VALUE;
     }
-    Mutex::Autolock _l(mStateLock);
-    const auto display = getDisplayDeviceLocked(displayToken);
+    const auto display = getDisplayDevice(displayToken);
     if (!display || !display->getId()) {
         ALOGE("getDisplayedContentSamplingAttributes: Bad display token: %p", display.get());
         return BAD_VALUE;
@@ -1131,6 +1132,19 @@
                                                                  outDataspace, outComponentMask);
 }
 
+status_t SurfaceFlinger::setDisplayContentSamplingEnabled(const sp<IBinder>& displayToken,
+                                                          bool enable, uint8_t componentMask,
+                                                          uint64_t maxFrames) const {
+    const auto display = getDisplayDevice(displayToken);
+    if (!display || !display->getId()) {
+        ALOGE("setDisplayContentSamplingEnabled: Bad display token: %p", display.get());
+        return BAD_VALUE;
+    }
+
+    return getHwComposer().setDisplayContentSamplingEnabled(*display->getId(), enable,
+                                                            componentMask, maxFrames);
+}
+
 status_t SurfaceFlinger::enableVSyncInjections(bool enable) {
     postMessageSync(new LambdaMessage([&] {
         Mutex::Autolock _l(mStateLock);
@@ -1526,7 +1540,7 @@
             mFrameMissedCount += frameMissed;
             ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
             if (frameMissed) {
-                mTimeStats.incrementMissedFrames();
+                mTimeStats->incrementMissedFrames();
                 if (mPropagateBackpressure) {
                     signalLayerUpdate();
                     break;
@@ -1951,12 +1965,12 @@
         mAnimFrameTracker.advanceFrame();
     }
 
-    mTimeStats.incrementTotalFrames();
+    mTimeStats->incrementTotalFrames();
     if (mHadClientComposition) {
-        mTimeStats.incrementClientCompositionFrames();
+        mTimeStats->incrementClientCompositionFrames();
     }
 
-    mTimeStats.setPresentFenceGlobal(presentFenceTime);
+    mTimeStats->setPresentFenceGlobal(presentFenceTime);
 
     if (display && getHwComposer().isConnected(*display->getId()) && !display->isPoweredOn()) {
         return;
@@ -4056,7 +4070,7 @@
     }
 
     if (display->isPrimary()) {
-        mTimeStats.setPowerMode(mode);
+        mTimeStats->setPowerMode(mode);
     }
 
     ALOGD("Finished setting power mode %d on display %s", mode, to_string(*displayId).c_str());
@@ -4199,7 +4213,7 @@
 
             if ((index < numArgs) && (args[index] == String16("--timestats"))) {
                 index++;
-                mTimeStats.parseArgs(asProto, args, index, result);
+                mTimeStats->parseArgs(asProto, args, index, result);
                 dumpAll = false;
             }
         }
@@ -4776,7 +4790,8 @@
         case SET_ACTIVE_COLOR_MODE:
         case INJECT_VSYNC:
         case SET_POWER_MODE:
-        case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: {
+        case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES:
+        case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: {
             if (!callingThreadHasUnscopedSurfaceFlingerAccess()) {
                 IPCThreadState* ipc = IPCThreadState::self();
                 ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d",
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index eff5fca..fe2f1c26 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -72,7 +72,6 @@
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VSyncModulator.h"
-#include "TimeStats/TimeStats.h"
 
 #include <map>
 #include <mutex>
@@ -103,6 +102,7 @@
 class Layer;
 class Surface;
 class SurfaceFlingerBE;
+class TimeStats;
 class VSyncSource;
 struct CompositionInfo;
 
@@ -483,6 +483,9 @@
     virtual status_t getDisplayedContentSamplingAttributes(
             const sp<IBinder>& display, ui::PixelFormat* outFormat, ui::Dataspace* outDataspace,
             uint8_t* outComponentMask) const override;
+    virtual status_t setDisplayContentSamplingEnabled(const sp<IBinder>& display, bool enable,
+                                                      uint8_t componentMask,
+                                                      uint64_t maxFrames) const override;
 
     /* ------------------------------------------------------------------------
      * DeathRecipient interface
@@ -926,7 +929,7 @@
     std::unique_ptr<SurfaceInterceptor> mInterceptor{mFactory.createSurfaceInterceptor(this)};
     SurfaceTracing mTracing;
     LayerStats mLayerStats;
-    TimeStats& mTimeStats = TimeStats::getInstance();
+    std::unique_ptr<TimeStats> mTimeStats;
     bool mUseHwcVirtualDisplays = false;
     std::atomic<uint32_t> mFrameMissedCount{0};
 
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.cpp b/services/surfaceflinger/SurfaceFlingerFactory.cpp
index 1261aee..88649e3 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerFactory.cpp
@@ -33,6 +33,7 @@
 #include "Scheduler/EventControlThread.h"
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/Scheduler.h"
+#include "TimeStats/TimeStats.h"
 
 namespace android::surfaceflinger {
 
@@ -116,6 +117,10 @@
         sp<ColorLayer> createColorLayer(const LayerCreationArgs& args) override {
             return new ColorLayer(args);
         }
+
+        std::unique_ptr<TimeStats> createTimeStats() override {
+            return std::make_unique<TimeStats>();
+        }
     };
     static Factory factory;
 
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 496bbf0..1c27cc7 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -44,6 +44,7 @@
 class StartPropertySetThread;
 class SurfaceFlinger;
 class SurfaceInterceptor;
+class TimeStats;
 
 struct DisplayDeviceCreationArgs;
 struct LayerCreationArgs;
@@ -82,6 +83,8 @@
     virtual sp<ColorLayer> createColorLayer(const LayerCreationArgs& args) = 0;
     virtual sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) = 0;
 
+    virtual std::unique_ptr<TimeStats> createTimeStats() = 0;
+
 protected:
     ~Factory() = default;
 };
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 9730e8c..2b9f5c8 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -32,11 +32,6 @@
 
 namespace android {
 
-TimeStats& TimeStats::getInstance() {
-    static TimeStats sInstance;
-    return sInstance;
-}
-
 void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, size_t& index,
                           String8& result) {
     ATRACE_CALL();
@@ -268,11 +263,9 @@
     if (!mTimeStatsTracker.count(layerID)) return;
     LayerRecord& layerRecord = mTimeStatsTracker[layerID];
     if (layerRecord.timeRecords.size() == MAX_NUM_TIME_RECORDS) {
-        ALOGE("[%d]-[%s]-timeRecords is already at its maximum size[%zu]. Please file a bug.",
+        ALOGE("[%d]-[%s]-timeRecords is at its maximum size[%zu]. Ignore this when unittesting.",
               layerID, layerRecord.layerName.c_str(), MAX_NUM_TIME_RECORDS);
-        layerRecord.timeRecords.clear();
-        layerRecord.prevTimeRecord.ready = false;
-        layerRecord.waitData = -1;
+        mTimeStatsTracker.erase(layerID);
         return;
     }
     // For most media content, the acquireFence is invalid because the buffer is
@@ -283,7 +276,9 @@
                     {
                             .frameNumber = frameNumber,
                             .postTime = postTime,
+                            .latchTime = postTime,
                             .acquireTime = postTime,
+                            .desiredTime = postTime,
                     },
     };
     layerRecord.timeRecords.push_back(timeRecord);
@@ -394,18 +389,6 @@
     flushAvailableRecordsToStatsLocked(layerID);
 }
 
-void TimeStats::onDisconnect(int32_t layerID) {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-    ALOGV("[%d]-onDisconnect", layerID);
-
-    std::lock_guard<std::mutex> lock(mMutex);
-    if (!mTimeStatsTracker.count(layerID)) return;
-    flushAvailableRecordsToStatsLocked(layerID);
-    mTimeStatsTracker.erase(layerID);
-}
-
 void TimeStats::onDestroy(int32_t layerID) {
     if (!mEnabled.load()) return;
 
@@ -414,25 +397,9 @@
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (!mTimeStatsTracker.count(layerID)) return;
-    flushAvailableRecordsToStatsLocked(layerID);
     mTimeStatsTracker.erase(layerID);
 }
 
-void TimeStats::clearLayerRecord(int32_t layerID) {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-    ALOGV("[%d]-clearLayerRecord", layerID);
-
-    std::lock_guard<std::mutex> lock(mMutex);
-    if (!mTimeStatsTracker.count(layerID)) return;
-    LayerRecord& layerRecord = mTimeStatsTracker[layerID];
-    layerRecord.timeRecords.clear();
-    layerRecord.prevTimeRecord.ready = false;
-    layerRecord.waitData = -1;
-    layerRecord.droppedFrames = 0;
-}
-
 void TimeStats::removeTimeRecord(int32_t layerID, uint64_t frameNumber) {
     if (!mEnabled.load()) return;
 
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 184bf40..0b24c46 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -38,10 +38,6 @@
 class String8;
 
 class TimeStats {
-    // TODO(zzyiwei): Bound the timeStatsTracker with weighted LRU
-    // static const size_t MAX_NUM_LAYER_RECORDS = 200;
-    static const size_t MAX_NUM_TIME_RECORDS = 64;
-
     struct FrameTime {
         uint64_t frameNumber = 0;
         nsecs_t postTime = 0;
@@ -80,8 +76,12 @@
     };
 
 public:
-    static TimeStats& getInstance();
+    TimeStats() = default;
+    ~TimeStats() = default;
+
     void parseArgs(bool asProto, const Vector<String16>& args, size_t& index, String8& result);
+    bool isEnabled();
+
     void incrementTotalFrames();
     void incrementMissedFrames();
     void incrementClientCompositionFrames();
@@ -96,21 +96,19 @@
     void setPresentTime(int32_t layerID, uint64_t frameNumber, nsecs_t presentTime);
     void setPresentFence(int32_t layerID, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& presentFence);
-    // On producer disconnect with BufferQueue.
-    void onDisconnect(int32_t layerID);
-    // On layer tear down.
+    // Clean up the layer record
     void onDestroy(int32_t layerID);
-    // When SF is cleaning up the queue, clear the LayerRecord as well.
-    void clearLayerRecord(int32_t layerID);
     // If SF skips or rejects a buffer, remove the corresponding TimeRecord.
     void removeTimeRecord(int32_t layerID, uint64_t frameNumber);
 
     void setPowerMode(int32_t powerMode);
     void setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence);
 
-private:
-    TimeStats() = default;
+    // TODO(zzyiwei): Bound the timeStatsTracker with weighted LRU
+    // static const size_t MAX_NUM_LAYER_RECORDS = 200;
+    static const size_t MAX_NUM_TIME_RECORDS = 64;
 
+private:
     bool recordReadyLocked(int32_t layerID, TimeRecord* timeRecord);
     void flushAvailableRecordsToStatsLocked(int32_t layerID);
     void flushPowerTimeLocked();
@@ -119,7 +117,6 @@
     void enable();
     void disable();
     void clear();
-    bool isEnabled();
     void dump(bool asProto, std::optional<uint32_t> maxLayers, String8& result);
 
     std::atomic<bool> mEnabled = false;
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 99de216..1a9249c 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -333,8 +333,9 @@
 
     virtual sp<SurfaceControl> createLayer(const sp<SurfaceComposerClient>& client,
                                            const char* name, uint32_t width, uint32_t height,
-                                           uint32_t flags = 0) {
-        auto layer = createSurface(client, name, width, height, PIXEL_FORMAT_RGBA_8888, flags);
+                                           uint32_t flags = 0, SurfaceControl* parent = nullptr) {
+        auto layer =
+                createSurface(client, name, width, height, PIXEL_FORMAT_RGBA_8888, flags, parent);
 
         Transaction t;
         t.setLayerStack(layer, mDisplayLayerStack).setLayer(layer, mLayerZBase);
@@ -358,8 +359,8 @@
     }
 
     virtual sp<SurfaceControl> createLayer(const char* name, uint32_t width, uint32_t height,
-                                           uint32_t flags = 0) {
-        return createLayer(mClient, name, width, height, flags);
+                                           uint32_t flags = 0, SurfaceControl* parent = nullptr) {
+        return createLayer(mClient, name, width, height, flags, parent);
     }
 
     ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) {
@@ -542,12 +543,12 @@
     LayerTypeTransactionTest() { mLayerType = GetParam(); }
 
     sp<SurfaceControl> createLayer(const char* name, uint32_t width, uint32_t height,
-                                   uint32_t flags = 0) override {
+                                   uint32_t flags = 0, SurfaceControl* parent = nullptr) override {
         // if the flags already have a layer type specified, return an error
         if (flags & ISurfaceComposerClient::eFXSurfaceMask) {
             return nullptr;
         }
-        return LayerTransactionTest::createLayer(name, width, height, flags | mLayerType);
+        return LayerTransactionTest::createLayer(name, width, height, flags | mLayerType, parent);
     }
 
     void fillLayerColor(const sp<SurfaceControl>& layer, const Color& color, int32_t bufferWidth,
@@ -2243,6 +2244,122 @@
     }
 }
 
+TEST_F(LayerTransactionTest, SetColorTransformOnParent) {
+    sp<SurfaceControl> parentLayer;
+    sp<SurfaceControl> colorLayer;
+    ASSERT_NO_FATAL_FAILURE(parentLayer = createLayer("parent", 0 /* buffer width */,
+                                                      0 /* buffer height */,
+                                                      ISurfaceComposerClient::eFXSurfaceContainer));
+    ASSERT_NO_FATAL_FAILURE(
+            colorLayer = createLayer("test", 0 /* buffer width */, 0 /* buffer height */,
+                                     ISurfaceComposerClient::eFXSurfaceColor, parentLayer.get()));
+
+    Transaction()
+            .setCrop_legacy(parentLayer, Rect(0, 0, 100, 100))
+            .setCrop_legacy(colorLayer, Rect(0, 0, 32, 32))
+            .setLayer(parentLayer, mLayerZBase + 1)
+            .apply();
+    {
+        SCOPED_TRACE("default color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+    }
+
+    const half3 color(50.0f / 255.0f, 100.0f / 255.0f, 150.0f / 255.0f);
+    half3 expected = color;
+    mat3 matrix;
+    matrix[0][0] = 0.3; matrix[1][0] = 0.59; matrix[2][0] = 0.11;
+    matrix[0][1] = 0.3; matrix[1][1] = 0.59; matrix[2][1] = 0.11;
+    matrix[0][2] = 0.3; matrix[1][2] = 0.59; matrix[2][2] = 0.11;
+
+    // degamma before applying the matrix
+    if (mColorManagementUsed) {
+        ColorTransformHelper::DegammaColor(expected);
+    }
+
+    ColorTransformHelper::applyMatrix(expected, matrix);
+
+    if (mColorManagementUsed) {
+        ColorTransformHelper::GammaColor(expected);
+    }
+
+    const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
+                                 uint8_t(expected.b * 255), 255};
+
+    // this is handwavy, but the precison loss scaled by 255 (8-bit per
+    // channel) should be less than one
+    const uint8_t tolerance = 1;
+
+    Transaction()
+            .setColor(colorLayer, color)
+            .setColorTransform(parentLayer, matrix, vec3())
+            .apply();
+    {
+        SCOPED_TRACE("new color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), expectedColor, tolerance);
+    }
+}
+
+TEST_F(LayerTransactionTest, SetColorTransformOnChildAndParent) {
+    sp<SurfaceControl> parentLayer;
+    sp<SurfaceControl> colorLayer;
+    ASSERT_NO_FATAL_FAILURE(parentLayer = createLayer("parent", 0 /* buffer width */,
+                                                      0 /* buffer height */,
+                                                      ISurfaceComposerClient::eFXSurfaceContainer));
+    ASSERT_NO_FATAL_FAILURE(
+            colorLayer = createLayer("test", 0 /* buffer width */, 0 /* buffer height */,
+                                     ISurfaceComposerClient::eFXSurfaceColor, parentLayer.get()));
+
+    Transaction()
+            .setCrop_legacy(parentLayer, Rect(0, 0, 100, 100))
+            .setCrop_legacy(colorLayer, Rect(0, 0, 32, 32))
+            .setLayer(parentLayer, mLayerZBase + 1)
+            .apply();
+    {
+        SCOPED_TRACE("default color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+    }
+
+    const half3 color(50.0f / 255.0f, 100.0f / 255.0f, 150.0f / 255.0f);
+    half3 expected = color;
+    mat3 matrixChild;
+    matrixChild[0][0] = 0.3; matrixChild[1][0] = 0.59; matrixChild[2][0] = 0.11;
+    matrixChild[0][1] = 0.3; matrixChild[1][1] = 0.59; matrixChild[2][1] = 0.11;
+    matrixChild[0][2] = 0.3; matrixChild[1][2] = 0.59; matrixChild[2][2] = 0.11;
+    mat3 matrixParent;
+    matrixParent[0][0] = 0.2; matrixParent[1][0] = 0.4; matrixParent[2][0] = 0.10;
+    matrixParent[0][1] = 0.2; matrixParent[1][1] = 0.4; matrixParent[2][1] = 0.10;
+    matrixParent[0][2] = 0.2; matrixParent[1][2] = 0.4; matrixParent[2][2] = 0.10;
+
+    // degamma before applying the matrix
+    if (mColorManagementUsed) {
+        ColorTransformHelper::DegammaColor(expected);
+    }
+
+    ColorTransformHelper::applyMatrix(expected, matrixChild);
+    ColorTransformHelper::applyMatrix(expected, matrixParent);
+
+    if (mColorManagementUsed) {
+        ColorTransformHelper::GammaColor(expected);
+    }
+
+    const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
+                                 uint8_t(expected.b * 255), 255};
+
+    // this is handwavy, but the precison loss scaled by 255 (8-bit per
+    // channel) should be less than one
+    const uint8_t tolerance = 1;
+
+    Transaction()
+            .setColor(colorLayer, color)
+            .setColorTransform(parentLayer, matrixParent, vec3())
+            .setColorTransform(colorLayer, matrixChild, vec3())
+            .apply();
+    {
+        SCOPED_TRACE("new color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), expectedColor, tolerance);
+    }
+}
+
 class ExpectedResult {
 public:
     enum Transaction {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 28f3ef2..42b7146 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -32,6 +32,7 @@
         "LayerHistoryTest.cpp",
         "SchedulerTest.cpp",
         "SchedulerUtilsTest.cpp",
+        "TimeStatsTest.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockDisplaySurface.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 85c835e..6d4f7ef 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -28,6 +28,8 @@
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceInterceptor.h"
 
+#include "TimeStats/TimeStats.h"
+
 namespace android {
 
 class EventThread;
@@ -131,6 +133,11 @@
         return nullptr;
     }
 
+    std::unique_ptr<TimeStats> createTimeStats() override {
+        // TODO: Use test-fixture controlled factory
+        return std::make_unique<TimeStats>();
+    }
+
     using CreateBufferQueueFunction =
             std::function<void(sp<IGraphicBufferProducer>* /* outProducer */,
                                sp<IGraphicBufferConsumer>* /* outConsumer */,
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
new file mode 100644
index 0000000..186ed79
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2018 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 <gtest/gtest.h>
+
+#include <log/log.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <random>
+
+#include "TimeStats/TimeStats.h"
+
+using namespace android::surfaceflinger;
+using namespace google::protobuf;
+
+namespace android {
+namespace {
+
+// clang-format off
+#define FMT_PROTO          true
+#define FMT_STRING         false
+#define LAYER_ID_0         0
+#define LAYER_ID_1         1
+#define LAYER_ID_INVALID   -1
+#define NUM_LAYERS         1
+#define NUM_LAYERS_INVALID "INVALID"
+
+enum InputCommand : int32_t {
+    ENABLE                 = 0,
+    DISABLE                = 1,
+    CLEAR                  = 2,
+    DUMP_ALL               = 3,
+    DUMP_MAXLAYERS_1       = 4,
+    DUMP_MAXLAYERS_INVALID = 5,
+    INPUT_COMMAND_BEGIN    = ENABLE,
+    INPUT_COMMAND_END      = DUMP_MAXLAYERS_INVALID,
+    INPUT_COMMAND_RANGE    = INPUT_COMMAND_END - INPUT_COMMAND_BEGIN + 1,
+};
+
+enum TimeStamp : int32_t {
+    POST                   = 0,
+    ACQUIRE                = 1,
+    ACQUIRE_FENCE          = 2,
+    LATCH                  = 3,
+    DESIRED                = 4,
+    PRESENT                = 5,
+    PRESENT_FENCE          = 6,
+    TIME_STAMP_BEGIN       = POST,
+    TIME_STAMP_END         = PRESENT,
+    TIME_STAMP_RANGE       = TIME_STAMP_END - TIME_STAMP_BEGIN + 1,
+};
+
+static const TimeStamp NORMAL_SEQUENCE[] = {
+        TimeStamp::POST,
+        TimeStamp::ACQUIRE,
+        TimeStamp::LATCH,
+        TimeStamp::DESIRED,
+        TimeStamp::PRESENT,
+};
+
+static const TimeStamp NORMAL_SEQUENCE_2[] = {
+        TimeStamp::POST,
+        TimeStamp::ACQUIRE_FENCE,
+        TimeStamp::LATCH,
+        TimeStamp::DESIRED,
+        TimeStamp::PRESENT_FENCE,
+};
+
+static const TimeStamp UNORDERED_SEQUENCE[] = {
+        TimeStamp::ACQUIRE,
+        TimeStamp::LATCH,
+        TimeStamp::POST,
+        TimeStamp::DESIRED,
+        TimeStamp::PRESENT,
+};
+
+static const TimeStamp INCOMPLETE_SEQUENCE[] = {
+        TimeStamp::POST,
+};
+// clang-format on
+
+class TimeStatsTest : public testing::Test {
+public:
+    TimeStatsTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    ~TimeStatsTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    std::string inputCommand(InputCommand cmd, bool useProto);
+
+    void setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts);
+
+    int32_t genRandomInt32(int32_t begin, int32_t end);
+
+    template <size_t N>
+    void insertTimeRecord(const TimeStamp (&sequence)[N], int32_t id, uint64_t frameNumber,
+                          nsecs_t ts) {
+        for (size_t i = 0; i < N; i++, ts += 1000000) {
+            setTimeStamp(sequence[i], id, frameNumber, ts);
+        }
+    }
+
+    std::mt19937 mRandomEngine = std::mt19937(std::random_device()());
+    std::unique_ptr<TimeStats> mTimeStats = std::make_unique<TimeStats>();
+};
+
+std::string TimeStatsTest::inputCommand(InputCommand cmd, bool useProto) {
+    size_t index = 0;
+    String8 result;
+    Vector<String16> args;
+
+    switch (cmd) {
+        case InputCommand::ENABLE:
+            args.push_back(String16("-enable"));
+            break;
+        case InputCommand::DISABLE:
+            args.push_back(String16("-disable"));
+            break;
+        case InputCommand::CLEAR:
+            args.push_back(String16("-clear"));
+            break;
+        case InputCommand::DUMP_ALL:
+            args.push_back(String16("-dump"));
+            break;
+        case InputCommand::DUMP_MAXLAYERS_1:
+            args.push_back(String16("-dump"));
+            args.push_back(String16("-maxlayers"));
+            args.push_back(String16(std::to_string(NUM_LAYERS).c_str()));
+            break;
+        case InputCommand::DUMP_MAXLAYERS_INVALID:
+            args.push_back(String16("-dump"));
+            args.push_back(String16("-maxlayers"));
+            args.push_back(String16(NUM_LAYERS_INVALID));
+            break;
+        default:
+            ALOGD("Invalid control command");
+    }
+
+    EXPECT_NO_FATAL_FAILURE(mTimeStats->parseArgs(useProto, args, index, result));
+    return std::string(result.string(), result.size());
+}
+
+static std::string genLayerName(int32_t layerID) {
+    return (layerID < 0 ? "invalid.dummy" : "com.dummy#") + std::to_string(layerID);
+}
+
+void TimeStatsTest::setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts) {
+    switch (type) {
+        case TimeStamp::POST:
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setPostTime(id, frameNumber, genLayerName(id), ts));
+            break;
+        case TimeStamp::ACQUIRE:
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setAcquireTime(id, frameNumber, ts));
+            break;
+        case TimeStamp::ACQUIRE_FENCE:
+            ASSERT_NO_FATAL_FAILURE(
+                    mTimeStats->setAcquireFence(id, frameNumber, std::make_shared<FenceTime>(ts)));
+            break;
+        case TimeStamp::LATCH:
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setLatchTime(id, frameNumber, ts));
+            break;
+        case TimeStamp::DESIRED:
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setDesiredTime(id, frameNumber, ts));
+            break;
+        case TimeStamp::PRESENT:
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setPresentTime(id, frameNumber, ts));
+            break;
+        case TimeStamp::PRESENT_FENCE:
+            ASSERT_NO_FATAL_FAILURE(
+                    mTimeStats->setPresentFence(id, frameNumber, std::make_shared<FenceTime>(ts)));
+            break;
+        default:
+            ALOGD("Invalid timestamp type");
+    }
+}
+
+int32_t TimeStatsTest::genRandomInt32(int32_t begin, int32_t end) {
+    std::uniform_int_distribution<int32_t> distr(begin, end);
+    return distr(mRandomEngine);
+}
+
+TEST_F(TimeStatsTest, canEnableAndDisableTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+    ASSERT_TRUE(mTimeStats->isEnabled());
+
+    EXPECT_TRUE(inputCommand(InputCommand::DISABLE, FMT_STRING).empty());
+    ASSERT_FALSE(mTimeStats->isEnabled());
+}
+
+TEST_F(TimeStatsTest, canIncreaseGlobalStats) {
+    constexpr size_t TOTAL_FRAMES = 5;
+    constexpr size_t MISSED_FRAMES = 4;
+    constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
+
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    for (size_t i = 0; i < TOTAL_FRAMES; i++) {
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementTotalFrames());
+    }
+    for (size_t i = 0; i < MISSED_FRAMES; i++) {
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementMissedFrames());
+    }
+    for (size_t i = 0; i < CLIENT_COMPOSITION_FRAMES; i++) {
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionFrames());
+    }
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_TRUE(globalProto.has_total_frames());
+    EXPECT_EQ(TOTAL_FRAMES, globalProto.total_frames());
+    ASSERT_TRUE(globalProto.has_missed_frames());
+    EXPECT_EQ(MISSED_FRAMES, globalProto.missed_frames());
+    ASSERT_TRUE(globalProto.has_client_composition_frames());
+    EXPECT_EQ(CLIENT_COMPOSITION_FRAMES, globalProto.client_composition_frames());
+}
+
+TEST_F(TimeStatsTest, canInsertGlobalPresentToPresent) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(1000000)));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(2000000)));
+
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000)));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000)));
+
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->setPowerMode(HWC_POWER_MODE_OFF));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(6000000)));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(8000000)));
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.present_to_present_size());
+    const SFTimeStatsHistogramBucketProto& histogramProto = globalProto.present_to_present().Get(0);
+    EXPECT_EQ(1, histogramProto.frame_count());
+    EXPECT_EQ(2, histogramProto.time_millis());
+}
+
+TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE_2, LAYER_ID_0, 2, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_layer_name());
+    EXPECT_EQ(genLayerName(LAYER_ID_0), layerProto.layer_name());
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(1, layerProto.total_frames());
+    ASSERT_EQ(6, layerProto.deltas_size());
+    for (const SFTimeStatsDeltaProto& deltaProto : layerProto.deltas()) {
+        ASSERT_EQ(1, deltaProto.histograms_size());
+        const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms().Get(0);
+        EXPECT_EQ(1, histogramProto.frame_count());
+        if ("post2acquire" == deltaProto.delta_name()) {
+            EXPECT_EQ(1, histogramProto.time_millis());
+        } else if ("post2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(4, histogramProto.time_millis());
+        } else if ("acquire2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(3, histogramProto.time_millis());
+        } else if ("latch2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(2, histogramProto.time_millis());
+        } else if ("desired2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(1, histogramProto.time_millis());
+        } else if ("present2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(1, histogramProto.time_millis());
+        } else {
+            FAIL() << "Unknown delta_name: " << deltaProto.delta_name();
+        }
+    }
+}
+
+TEST_F(TimeStatsTest, canNotInsertInvalidLayerNameTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_INVALID, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE_2, LAYER_ID_INVALID, 2, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(0, globalProto.stats_size());
+}
+
+TEST_F(TimeStatsTest, canInsertMultipleLayersTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    EXPECT_EQ(2, globalProto.stats_size());
+}
+
+TEST_F(TimeStatsTest, canInsertUnorderedLayerTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(UNORDERED_SEQUENCE, LAYER_ID_0, 2, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_layer_name());
+    EXPECT_EQ(genLayerName(LAYER_ID_0), layerProto.layer_name());
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(1, layerProto.total_frames());
+    ASSERT_EQ(6, layerProto.deltas_size());
+    for (const SFTimeStatsDeltaProto& deltaProto : layerProto.deltas()) {
+        ASSERT_EQ(1, deltaProto.histograms_size());
+        const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms().Get(0);
+        EXPECT_EQ(1, histogramProto.frame_count());
+        if ("post2acquire" == deltaProto.delta_name()) {
+            EXPECT_EQ(0, histogramProto.time_millis());
+        } else if ("post2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(2, histogramProto.time_millis());
+        } else if ("acquire2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(2, histogramProto.time_millis());
+        } else if ("latch2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(2, histogramProto.time_millis());
+        } else if ("desired2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(1, histogramProto.time_millis());
+        } else if ("present2present" == deltaProto.delta_name()) {
+            EXPECT_EQ(1, histogramProto.time_millis());
+        } else {
+            FAIL() << "Unknown delta_name: " << deltaProto.delta_name();
+        }
+    }
+}
+
+TEST_F(TimeStatsTest, canRemoveTimeRecord) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(INCOMPLETE_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->removeTimeRecord(0, 2));
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(1, layerProto.total_frames());
+}
+
+TEST_F(TimeStatsTest, canRecoverFromIncompleteTimeRecordError) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    uint64_t frameNumber = 1;
+    nsecs_t ts = 1000000;
+    insertTimeRecord(INCOMPLETE_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    for (size_t i = 0; i < TimeStats::MAX_NUM_TIME_RECORDS + 2; i++) {
+        frameNumber++;
+        ts += 1000000;
+        insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, frameNumber, ts);
+    }
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(1, layerProto.total_frames());
+}
+
+TEST_F(TimeStatsTest, layerTimeStatsOnDestroy) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->onDestroy(0));
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(1, layerProto.total_frames());
+}
+
+TEST_F(TimeStatsTest, canClearTimeStats) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementTotalFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementMissedFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(1000000)));
+    ASSERT_NO_FATAL_FAILURE(
+            mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(2000000)));
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+
+    EXPECT_TRUE(inputCommand(InputCommand::CLEAR, FMT_STRING).empty());
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    EXPECT_EQ(0, globalProto.total_frames());
+    EXPECT_EQ(0, globalProto.missed_frames());
+    EXPECT_EQ(0, globalProto.client_composition_frames());
+    EXPECT_EQ(0, globalProto.present_to_present_size());
+    EXPECT_EQ(0, globalProto.stats_size());
+}
+
+TEST_F(TimeStatsTest, canDumpWithMaxLayers) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 2000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 3, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(
+            globalProto.ParseFromString(inputCommand(InputCommand::DUMP_MAXLAYERS_1, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.stats_size());
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    ASSERT_TRUE(layerProto.has_layer_name());
+    EXPECT_EQ(genLayerName(LAYER_ID_1), layerProto.layer_name());
+    ASSERT_TRUE(layerProto.has_total_frames());
+    EXPECT_EQ(2, layerProto.total_frames());
+}
+
+TEST_F(TimeStatsTest, canDumpWithInvalidMaxLayers) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(
+            inputCommand(InputCommand::DUMP_MAXLAYERS_INVALID, FMT_PROTO)));
+
+    ASSERT_EQ(0, globalProto.stats_size());
+}
+
+TEST_F(TimeStatsTest, canSurviveMonkey) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    for (size_t i = 0; i < 10000000; ++i) {
+        const int32_t layerID = genRandomInt32(-1, 10);
+        const int32_t frameNumber = genRandomInt32(1, 10);
+        switch (genRandomInt32(0, 100)) {
+            case 0:
+                ALOGV("removeTimeRecord");
+                ASSERT_NO_FATAL_FAILURE(mTimeStats->removeTimeRecord(layerID, frameNumber));
+                continue;
+            case 1:
+                ALOGV("onDestroy");
+                ASSERT_NO_FATAL_FAILURE(mTimeStats->onDestroy(layerID));
+                continue;
+        }
+        TimeStamp type = static_cast<TimeStamp>(genRandomInt32(TIME_STAMP_BEGIN, TIME_STAMP_END));
+        const int32_t ts = genRandomInt32(1, 1000000000);
+        ALOGV("type[%d], layerID[%d], frameNumber[%d], ts[%d]", type, layerID, frameNumber, ts);
+        setTimeStamp(type, layerID, frameNumber, ts);
+    }
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 5a46c26..68fd8b4 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -115,6 +115,7 @@
     MOCK_METHOD3(setLayerColorTransform, Error(Display, Layer, const float*));
     MOCK_METHOD4(getDisplayedContentSamplingAttributes,
                  Error(Display, PixelFormat*, Dataspace*, uint8_t*));
+    MOCK_METHOD4(setDisplayContentSamplingEnabled, Error(Display, bool, uint8_t, uint64_t));
     MOCK_METHOD2(getDisplayCapabilities, Error(Display, std::vector<DisplayCapability>*));
 };
 
diff --git a/services/vr/bufferhubd/Android.bp b/services/vr/bufferhubd/Android.bp
index 0a59f29..7a7e437 100644
--- a/services/vr/bufferhubd/Android.bp
+++ b/services/vr/bufferhubd/Android.bp
@@ -14,7 +14,6 @@
 
 sharedLibraries = [
     "libbase",
-    "libbinder",
     "libbufferhubservice",
     "libcutils",
     "libgtest_prod",
@@ -30,12 +29,9 @@
     name: "libbufferhubd",
     srcs: [
         "buffer_channel.cpp",
-        "buffer_client.cpp",
         "buffer_hub.cpp",
-        "buffer_hub_binder.cpp",
         "consumer_channel.cpp",
         "consumer_queue_channel.cpp",
-        "IBufferHub.cpp",
         "producer_channel.cpp",
         "producer_queue_channel.cpp",
     ],
@@ -45,10 +41,7 @@
         "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
     ],
     export_include_dirs: ["include"],
-    header_libs: [
-        "libdvr_headers",
-        "libnativewindow_headers",
-    ],
+    header_libs: ["libdvr_headers"],
     shared_libs: sharedLibraries,
     static_libs: [
         "libbufferhub",
diff --git a/services/vr/bufferhubd/IBufferHub.cpp b/services/vr/bufferhubd/IBufferHub.cpp
deleted file mode 100644
index 5980873..0000000
--- a/services/vr/bufferhubd/IBufferHub.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#include <log/log.h>
-#include <private/dvr/IBufferHub.h>
-
-namespace android {
-namespace dvr {
-
-class BpBufferHub : public BpInterface<IBufferHub> {
- public:
-  explicit BpBufferHub(const sp<IBinder>& impl)
-      : BpInterface<IBufferHub>(impl) {}
-
-  sp<IBufferClient> createBuffer(uint32_t width, uint32_t height,
-                                 uint32_t layer_count, uint32_t format,
-                                 uint64_t usage,
-                                 uint64_t user_metadata_size) override;
-
-  status_t importBuffer(uint64_t token, sp<IBufferClient>* outClient) override;
-};
-
-IMPLEMENT_META_INTERFACE(BufferHub, "android.dvr.IBufferHub");
-
-// Transaction code
-enum {
-  CREATE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
-  IMPORT_BUFFER,
-};
-
-sp<IBufferClient> BpBufferHub::createBuffer(uint32_t width, uint32_t height,
-                                            uint32_t layer_count,
-                                            uint32_t format, uint64_t usage,
-                                            uint64_t user_metadata_size) {
-  Parcel data, reply;
-  status_t ret = OK;
-  ret |= data.writeInterfaceToken(IBufferHub::getInterfaceDescriptor());
-  ret |= data.writeUint32(width);
-  ret |= data.writeUint32(height);
-  ret |= data.writeUint32(layer_count);
-  ret |= data.writeUint32(format);
-  ret |= data.writeUint64(usage);
-  ret |= data.writeUint64(user_metadata_size);
-
-  if (ret != OK) {
-    ALOGE("BpBufferHub::createBuffer: failed to write into parcel");
-    return nullptr;
-  }
-
-  ret = remote()->transact(CREATE_BUFFER, data, &reply);
-  if (ret == OK) {
-    return interface_cast<IBufferClient>(reply.readStrongBinder());
-  } else {
-    ALOGE("BpBufferHub::createBuffer: failed to transact; errno=%d", ret);
-    return nullptr;
-  }
-}
-
-status_t BpBufferHub::importBuffer(uint64_t token,
-                                   sp<IBufferClient>* outClient) {
-  Parcel data, reply;
-  status_t ret = OK;
-  ret |= data.writeInterfaceToken(IBufferHub::getInterfaceDescriptor());
-  ret |= data.writeUint64(token);
-  if (ret != OK) {
-    ALOGE("BpBufferHub::importBuffer: failed to write into parcel");
-    return ret;
-  }
-
-  ret = remote()->transact(IMPORT_BUFFER, data, &reply);
-  if (ret == OK) {
-    *outClient = interface_cast<IBufferClient>(reply.readStrongBinder());
-    return OK;
-  } else {
-    ALOGE("BpBufferHub::importBuffer: failed to transact; errno=%d", ret);
-    return ret;
-  }
-}
-
-status_t BnBufferHub::onTransact(uint32_t code, const Parcel& data,
-                                 Parcel* reply, uint32_t flags) {
-  switch (code) {
-    case CREATE_BUFFER: {
-      CHECK_INTERFACE(IBufferHub, data, reply);
-      uint32_t width = data.readUint32();
-      uint32_t height = data.readUint32();
-      uint32_t layer_count = data.readUint32();
-      uint32_t format = data.readUint32();
-      uint64_t usage = data.readUint64();
-      uint64_t user_metadata_size = data.readUint64();
-      sp<IBufferClient> ret = createBuffer(width, height, layer_count, format,
-                                           usage, user_metadata_size);
-      return reply->writeStrongBinder(IInterface::asBinder(ret));
-    }
-    case IMPORT_BUFFER: {
-      CHECK_INTERFACE(IBufferHub, data, reply);
-      uint64_t token = data.readUint64();
-      sp<IBufferClient> client;
-      status_t ret = importBuffer(token, &client);
-      if (ret == OK) {
-        return reply->writeStrongBinder(IInterface::asBinder(client));
-      } else {
-        return ret;
-      }
-    }
-    default:
-      // Should not reach except binder defined transactions such as dumpsys
-      return BBinder::onTransact(code, data, reply, flags);
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
\ No newline at end of file
diff --git a/services/vr/bufferhubd/buffer_channel.cpp b/services/vr/bufferhubd/buffer_channel.cpp
index 6c64bcd..cf072b6 100644
--- a/services/vr/bufferhubd/buffer_channel.cpp
+++ b/services/vr/bufferhubd/buffer_channel.cpp
@@ -52,8 +52,7 @@
       buffer_id(), /*consumer_count=*/0, buffer_node_->buffer_desc().width,
       buffer_node_->buffer_desc().height, buffer_node_->buffer_desc().layers,
       buffer_node_->buffer_desc().format, buffer_node_->buffer_desc().usage,
-      /*pending_count=*/0, /*state=*/0, /*signaled_mask=*/0,
-      /*index=*/0);
+      /*state=*/0, /*signaled_mask=*/0, /*index=*/0);
 }
 
 void BufferChannel::HandleImpulse(Message& /*message*/) {
diff --git a/services/vr/bufferhubd/buffer_client.cpp b/services/vr/bufferhubd/buffer_client.cpp
deleted file mode 100644
index f14faf7..0000000
--- a/services/vr/bufferhubd/buffer_client.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include <private/dvr/buffer_client.h>
-#include <private/dvr/buffer_hub_binder.h>
-
-namespace android {
-namespace dvr {
-
-status_t BufferClient::duplicate(uint64_t* outToken) {
-  if (!buffer_node_) {
-    // Should never happen
-    ALOGE("BufferClient::duplicate: node is missing.");
-    return UNEXPECTED_NULL;
-  }
-  return service_->registerToken(std::weak_ptr<BufferNode>(buffer_node_),
-                                 outToken);
-}
-
-}  // namespace dvr
-}  // namespace android
\ No newline at end of file
diff --git a/services/vr/bufferhubd/buffer_hub.cpp b/services/vr/bufferhubd/buffer_hub.cpp
index b73c47d..f50d292 100644
--- a/services/vr/bufferhubd/buffer_hub.cpp
+++ b/services/vr/bufferhubd/buffer_hub.cpp
@@ -56,8 +56,6 @@
   stream << " ";
   stream << std::setw(10) << "Usage";
   stream << " ";
-  stream << std::setw(9) << "Pending";
-  stream << " ";
   stream << std::setw(18) << "State";
   stream << " ";
   stream << std::setw(18) << "Signaled";
@@ -90,8 +88,6 @@
       stream << std::setw(8) << info.usage;
       stream << std::dec << std::setfill(' ');
       stream << " ";
-      stream << std::setw(9) << info.pending_count;
-      stream << " ";
       stream << "0x" << std::hex << std::setfill('0');
       stream << std::setw(16) << info.state;
       stream << " ";
diff --git a/services/vr/bufferhubd/buffer_hub_binder.cpp b/services/vr/bufferhubd/buffer_hub_binder.cpp
deleted file mode 100644
index 580433e..0000000
--- a/services/vr/bufferhubd/buffer_hub_binder.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-#include <stdio.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <log/log.h>
-#include <private/dvr/buffer_hub_binder.h>
-#include <private/dvr/buffer_node.h>
-
-namespace android {
-namespace dvr {
-
-status_t BufferHubBinderService::start(
-    const std::shared_ptr<BufferHubService>& pdx_service) {
-  IPCThreadState::self()->disableBackgroundScheduling(true);
-
-  sp<BufferHubBinderService> service = new BufferHubBinderService();
-  service->pdx_service_ = pdx_service;
-
-  // Not using BinderService::publish because need to get an instance of this
-  // class (above). Following code is the same as
-  // BinderService::publishAndJoinThreadPool
-  sp<IServiceManager> sm = defaultServiceManager();
-  status_t result = sm->addService(
-      String16(getServiceName()), service,
-      /*allowIsolated =*/false,
-      /*dump flags =*/IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
-  if (result != OK) {
-    ALOGE("Publishing bufferhubd failed with error %d", result);
-    return result;
-  }
-
-  sp<ProcessState> process_self(ProcessState::self());
-  process_self->startThreadPool();
-
-  return result;
-}
-
-status_t BufferHubBinderService::dump(int fd, const Vector<String16>& args) {
-  FILE* out = fdopen(dup(fd), "w");
-
-  // Currently not supporting args, so notify the user.
-  if (!args.isEmpty()) {
-    fprintf(out,
-            "Note: dumpsys bufferhubd currently does not support args."
-            "Input arguments are ignored.\n");
-  }
-
-  fprintf(out, "Binder service:\n");
-  // Active buffers
-  fprintf(out, "Active BufferClients: %zu\n", client_list_.size());
-  // TODO(b/117790952): print buffer information after BufferNode has it
-  // TODO(b/116526156): print more information once we have them
-
-  if (pdx_service_) {
-    fprintf(out, "\nPDX service:\n");
-    // BufferHubService::Dumpstate(size_t) is not actually using the param
-    // So just using 0 as the length
-    fprintf(out, "%s", pdx_service_->DumpState(0).c_str());
-  } else {
-    fprintf(out, "PDX service not registered or died.\n");
-  }
-
-  fclose(out);
-  return OK;
-}
-
-status_t BufferHubBinderService::registerToken(
-    const std::weak_ptr<BufferNode> node, uint64_t* outToken) {
-  do {
-    *outToken = token_engine_();
-  } while (token_map_.find(*outToken) != token_map_.end());
-
-  token_map_.emplace(*outToken, node);
-  return OK;
-}
-
-sp<IBufferClient> BufferHubBinderService::createBuffer(
-    uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
-    uint64_t usage, uint64_t user_metadata_size) {
-  std::shared_ptr<BufferNode> node = std::make_shared<BufferNode>(
-      width, height, layer_count, format, usage, user_metadata_size);
-
-  sp<BufferClient> client = new BufferClient(node, this);
-  // Add it to list for bookkeeping and dumpsys.
-  client_list_.push_back(client);
-
-  return client;
-}
-
-status_t BufferHubBinderService::importBuffer(uint64_t token,
-                                              sp<IBufferClient>* outClient) {
-  auto iter = token_map_.find(token);
-
-  if (iter == token_map_.end()) {  // Not found
-    ALOGE("BufferHubBinderService::importBuffer: token %" PRIu64
-          "does not exist.",
-          token);
-    return PERMISSION_DENIED;
-  }
-
-  if (iter->second.expired()) {  // Gone
-    ALOGW(
-        "BufferHubBinderService::importBuffer: the original node of token "
-        "%" PRIu64 "has gone.",
-        token);
-    token_map_.erase(iter);
-    return DEAD_OBJECT;
-  }
-
-  // Promote the weak_ptr
-  std::shared_ptr<BufferNode> node(iter->second);
-  if (!node) {
-    ALOGE("BufferHubBinderService::importBuffer: promote weak_ptr failed.");
-    token_map_.erase(iter);
-    return DEAD_OBJECT;
-  }
-
-  sp<BufferClient> client = new BufferClient(node, this);
-  *outClient = client;
-
-  token_map_.erase(iter);
-  client_list_.push_back(client);
-
-  return OK;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/bufferhubd/bufferhubd.cpp b/services/vr/bufferhubd/bufferhubd.cpp
index e44cc2a..50cb3b7 100644
--- a/services/vr/bufferhubd/bufferhubd.cpp
+++ b/services/vr/bufferhubd/bufferhubd.cpp
@@ -6,7 +6,6 @@
 #include <log/log.h>
 #include <pdx/service_dispatcher.h>
 #include <private/dvr/buffer_hub.h>
-#include <private/dvr/buffer_hub_binder.h>
 
 int main(int, char**) {
   int ret = -1;
@@ -40,10 +39,6 @@
   CHECK_ERROR(!pdx_service, error, "Failed to create bufferhub pdx service\n");
   dispatcher->AddService(pdx_service);
 
-  ret = android::dvr::BufferHubBinderService::start(pdx_service);
-  CHECK_ERROR(ret != android::OK, error,
-              "Failed to create bufferhub binder service\n");
-
   ret = dvrSetSchedulerClass(0, "graphics");
   CHECK_ERROR(ret < 0, error, "Failed to set thread priority");
 
diff --git a/services/vr/bufferhubd/consumer_channel.cpp b/services/vr/bufferhubd/consumer_channel.cpp
index 98ef917..f936e95 100644
--- a/services/vr/bufferhubd/consumer_channel.cpp
+++ b/services/vr/bufferhubd/consumer_channel.cpp
@@ -153,11 +153,10 @@
   }
 }
 
-bool ConsumerChannel::OnProducerPosted() {
+void ConsumerChannel::OnProducerPosted() {
   acquired_ = false;
   released_ = false;
   SignalAvailable();
-  return true;
 }
 
 void ConsumerChannel::OnProducerClosed() {
diff --git a/services/vr/bufferhubd/include/private/dvr/IBufferHub.h b/services/vr/bufferhubd/include/private/dvr/IBufferHub.h
deleted file mode 100644
index 502c6d6..0000000
--- a/services/vr/bufferhubd/include/private/dvr/IBufferHub.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef ANDROID_DVR_IBUFFERHUB_H
-#define ANDROID_DVR_IBUFFERHUB_H
-
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-#include <private/dvr/IBufferClient.h>
-
-namespace android {
-namespace dvr {
-
-class IBufferHub : public IInterface {
- public:
-  DECLARE_META_INTERFACE(BufferHub);
-
-  static const char* getServiceName() { return "bufferhubd"; }
-  virtual sp<IBufferClient> createBuffer(uint32_t width, uint32_t height,
-                                         uint32_t layer_count, uint32_t format,
-                                         uint64_t usage,
-                                         uint64_t user_metadata_size) = 0;
-
-  virtual status_t importBuffer(uint64_t token,
-                                sp<IBufferClient>* outClient) = 0;
-};
-
-class BnBufferHub : public BnInterface<IBufferHub> {
- public:
-  virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                              uint32_t flags = 0);
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_IBUFFERHUB_H
\ No newline at end of file
diff --git a/services/vr/bufferhubd/include/private/dvr/buffer_client.h b/services/vr/bufferhubd/include/private/dvr/buffer_client.h
deleted file mode 100644
index d79ec0a..0000000
--- a/services/vr/bufferhubd/include/private/dvr/buffer_client.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef ANDROID_DVR_BUFFERCLIENT_H
-#define ANDROID_DVR_BUFFERCLIENT_H
-
-#include <private/dvr/IBufferClient.h>
-#include <private/dvr/buffer_node.h>
-
-namespace android {
-namespace dvr {
-
-// Forward declaration to avoid circular dependency
-class BufferHubBinderService;
-
-class BufferClient : public BnBufferClient {
- public:
-  // Creates a server-side buffer client from an existing BufferNode. Note that
-  // this funciton takes ownership of the shared_ptr.
-  explicit BufferClient(std::shared_ptr<BufferNode> node,
-                        BufferHubBinderService* service)
-      : service_(service), buffer_node_(std::move(node)){};
-
-  // Binder IPC functions
-  bool isValid() override {
-    return buffer_node_ ? buffer_node_->IsValid() : false;
-  };
-
-  status_t duplicate(uint64_t* outToken) override;
-
- private:
-  // Hold a pointer to the service to bypass binder interface, as BufferClient
-  // and the service will be in the same process. Also, since service owns
-  // Client, if service dead the clients will be destroyed, so this pointer is
-  // guaranteed to be valid.
-  BufferHubBinderService* service_;
-
-  std::shared_ptr<BufferNode> buffer_node_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_IBUFFERCLIENT_H
\ No newline at end of file
diff --git a/services/vr/bufferhubd/include/private/dvr/buffer_hub.h b/services/vr/bufferhubd/include/private/dvr/buffer_hub.h
index e47ffa3..676617d 100644
--- a/services/vr/bufferhubd/include/private/dvr/buffer_hub.h
+++ b/services/vr/bufferhubd/include/private/dvr/buffer_hub.h
@@ -52,7 +52,6 @@
     uint32_t layer_count = 0;
     uint32_t format = 0;
     uint64_t usage = 0;
-    size_t pending_count = 0;
     uint64_t state = 0;
     uint64_t signaled_mask = 0;
     uint64_t index = 0;
@@ -63,8 +62,7 @@
 
     BufferInfo(int id, size_t consumer_count, uint32_t width, uint32_t height,
                uint32_t layer_count, uint32_t format, uint64_t usage,
-               size_t pending_count, uint64_t state, uint64_t signaled_mask,
-               uint64_t index)
+               uint64_t state, uint64_t signaled_mask, uint64_t index)
         : id(id),
           type(kProducerType),
           consumer_count(consumer_count),
@@ -73,7 +71,6 @@
           layer_count(layer_count),
           format(format),
           usage(usage),
-          pending_count(pending_count),
           state(state),
           signaled_mask(signaled_mask),
           index(index) {}
diff --git a/services/vr/bufferhubd/include/private/dvr/buffer_hub_binder.h b/services/vr/bufferhubd/include/private/dvr/buffer_hub_binder.h
deleted file mode 100644
index cf6124b..0000000
--- a/services/vr/bufferhubd/include/private/dvr/buffer_hub_binder.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#ifndef ANDROID_DVR_BUFFER_HUB_BINDER_H
-#define ANDROID_DVR_BUFFER_HUB_BINDER_H
-
-#include <random>
-#include <unordered_map>
-#include <vector>
-
-#include <binder/BinderService.h>
-#include <private/dvr/IBufferHub.h>
-#include <private/dvr/buffer_client.h>
-#include <private/dvr/buffer_hub.h>
-#include <private/dvr/buffer_node.h>
-
-namespace android {
-namespace dvr {
-
-class BufferHubBinderService : public BinderService<BufferHubBinderService>,
-                               public BnBufferHub {
- public:
-  static status_t start(const std::shared_ptr<BufferHubService>& pdx_service);
-  // Dumps bufferhub related information to given fd (usually stdout)
-  // usage: adb shell dumpsys bufferhubd
-  virtual status_t dump(int fd, const Vector<String16>& args) override;
-
-  // Marks a BufferNode to be duplicated.
-  // TODO(b/116681016): add importToken(int64_t)
-  status_t registerToken(const std::weak_ptr<BufferNode> node,
-                         uint64_t* outToken);
-
-  // Binder IPC functions
-  sp<IBufferClient> createBuffer(uint32_t width, uint32_t height,
-                                 uint32_t layer_count, uint32_t format,
-                                 uint64_t usage,
-                                 uint64_t user_metadata_size) override;
-
-  status_t importBuffer(uint64_t token, sp<IBufferClient>* outClient) override;
-
- private:
-  std::shared_ptr<BufferHubService> pdx_service_;
-
-  std::vector<sp<BufferClient>> client_list_;
-
-  // TODO(b/118180214): use a more secure implementation
-  std::mt19937_64 token_engine_;
-  // The mapping from token to a specific node. This is a many-to-one mapping.
-  // One node could be refered by 0 to multiple tokens.
-  std::unordered_map<uint64_t, std::weak_ptr<BufferNode>> token_map_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_BUFFER_HUB_BINDER_H
diff --git a/services/vr/bufferhubd/include/private/dvr/consumer_channel.h b/services/vr/bufferhubd/include/private/dvr/consumer_channel.h
index 3298529..de0f23c 100644
--- a/services/vr/bufferhubd/include/private/dvr/consumer_channel.h
+++ b/services/vr/bufferhubd/include/private/dvr/consumer_channel.h
@@ -26,7 +26,7 @@
   uint64_t client_state_mask() const { return client_state_mask_; }
   BufferInfo GetBufferInfo() const override;
 
-  bool OnProducerPosted();
+  void OnProducerPosted();
   void OnProducerClosed();
 
  private:
diff --git a/services/vr/bufferhubd/include/private/dvr/producer_channel.h b/services/vr/bufferhubd/include/private/dvr/producer_channel.h
index 9aead79..4734439 100644
--- a/services/vr/bufferhubd/include/private/dvr/producer_channel.h
+++ b/services/vr/bufferhubd/include/private/dvr/producer_channel.h
@@ -62,7 +62,6 @@
   pdx::Status<void> OnConsumerRelease(Message& message,
                                       LocalFence release_fence);
 
-  void DecrementPendingConsumers();
   void OnConsumerOrphaned(const uint64_t& consumer_state_mask);
 
   void AddConsumer(ConsumerChannel* channel);
@@ -74,9 +73,6 @@
 
  private:
   std::vector<ConsumerChannel*> consumer_channels_;
-  // This counts the number of consumers left to process this buffer. If this is
-  // zero then the producer can re-acquire ownership.
-  int pending_consumers_;
 
   IonBuffer buffer_;
 
diff --git a/services/vr/bufferhubd/producer_channel.cpp b/services/vr/bufferhubd/producer_channel.cpp
index 4b4301f..ab1d94c 100644
--- a/services/vr/bufferhubd/producer_channel.cpp
+++ b/services/vr/bufferhubd/producer_channel.cpp
@@ -56,7 +56,6 @@
                                  uint64_t usage, size_t user_metadata_size,
                                  int* error)
     : BufferHubChannel(service, channel_id, channel_id, kProducerType),
-      pending_consumers_(0),
       user_metadata_size_(user_metadata_size),
       metadata_buf_size_(BufferHubDefs::kMetadataHeaderSize +
                          user_metadata_size) {
@@ -183,7 +182,7 @@
 
   return BufferInfo(buffer_id(), consumer_channels_.size(), buffer_.width(),
                     buffer_.height(), buffer_.layer_count(), buffer_.format(),
-                    buffer_.usage(), pending_consumers_,
+                    buffer_.usage(),
                     buffer_state_->load(std::memory_order_acquire),
                     signaled_mask, metadata_header_->queue_index);
 }
@@ -370,8 +369,9 @@
           (consumer_state_mask & BufferHubDefs::kHighBitsMask);
     }
   }
-  if (update_buffer_state && consumer->OnProducerPosted())
-    pending_consumers_++;
+  if (update_buffer_state) {
+    consumer->OnProducerPosted();
+  }
 
   return {status.take()};
 }
@@ -424,13 +424,9 @@
   // Signal any interested consumers. If there are none, the buffer will stay
   // in posted state until a consumer comes online. This behavior guarantees
   // that no frame is silently dropped.
-  pending_consumers_ = 0;
   for (auto consumer : consumer_channels_) {
-    if (consumer->OnProducerPosted())
-      pending_consumers_++;
+    consumer->OnProducerPosted();
   }
-  ALOGD_IF(TRACE, "ProducerChannel::OnProducerPost: %d pending consumers",
-           pending_consumers_);
 
   return {};
 }
@@ -439,18 +435,6 @@
   ATRACE_NAME("ProducerChannel::OnGain");
   ALOGD_IF(TRACE, "ProducerChannel::OnGain: buffer_id=%d", buffer_id());
 
-  // There are still pending consumers, return busy.
-  if (pending_consumers_ > 0) {
-    ALOGE(
-        "ProducerChannel::OnGain: Producer (id=%d) is gaining a buffer that "
-        "still has %d pending consumer(s).",
-        buffer_id(), pending_consumers_);
-    // TODO(b/77153033): add gain_posted_buffer to the impulse args, and
-    // return busy if the function does not allow gaining posted buffer.
-    // TODO(b/119331650): remove this if check if pending_consumers_ is removed.
-    // return ErrorStatus(EBUSY);
-  }
-
   ClearAvailable();
   post_fence_.close();
   return {std::move(returned_fence_)};
@@ -547,48 +531,25 @@
     }
   }
 
-  DecrementPendingConsumers();
-  if (pending_consumers_ == 0 && orphaned_consumer_bit_mask_) {
-    ALOGW(
-        "%s: orphaned buffer detected during the this acquire/release cycle: "
-        "id=%d orphaned=0x%" PRIx64 " queue_index=%" PRIx64 ".",
-        __FUNCTION__, buffer_id(), orphaned_consumer_bit_mask_,
-        metadata_header_->queue_index);
-    orphaned_consumer_bit_mask_ = 0;
+  uint64_t current_buffer_state =
+      buffer_state_->load(std::memory_order_acquire);
+  if (BufferHubDefs::IsClientReleased(current_buffer_state,
+                                      ~orphaned_consumer_bit_mask_)) {
+    SignalAvailable();
+    if (orphaned_consumer_bit_mask_) {
+      ALOGW(
+          "%s: orphaned buffer detected during the this acquire/release cycle: "
+          "id=%d orphaned=0x%" PRIx64 " queue_index=%" PRIx64 ".",
+          __FUNCTION__, buffer_id(), orphaned_consumer_bit_mask_,
+          metadata_header_->queue_index);
+      orphaned_consumer_bit_mask_ = 0;
+    }
   }
 
-  ALOGE_IF(
-      pending_consumers_ && BufferHubDefs::IsBufferReleased(
-                                buffer_state_->load(std::memory_order_acquire)),
-      "ProducerChannel::OnConsumerRelease: buffer state inconsistent: "
-      "pending_consumers=%d, buffer buffer is in releaed state.",
-      pending_consumers_);
   return {};
 }
 
-void ProducerChannel::DecrementPendingConsumers() {
-  if (pending_consumers_ == 0) {
-    ALOGE("ProducerChannel::DecrementPendingConsumers: no pending consumer.");
-    return;
-  }
-
-  --pending_consumers_;
-  ALOGD_IF(TRACE, "%s: buffer_id=%d %d consumers left", __FUNCTION__,
-           buffer_id(), pending_consumers_);
-
-  if (pending_consumers_ == 0) {
-    ALOGD_IF(TRACE,
-             "%s: releasing last consumer: buffer_id=%d state=%" PRIx64 ".",
-             __FUNCTION__, buffer_id(),
-             buffer_state_->load(std::memory_order_acquire));
-    SignalAvailable();
-  }
-}
-
 void ProducerChannel::OnConsumerOrphaned(const uint64_t& consumer_state_mask) {
-  // Ignore the orphaned consumer.
-  DecrementPendingConsumers();
-
   // Remember the ignored consumer so that newly added consumer won't be
   // taking the same state mask as this orphaned consumer.
   ALOGE_IF(orphaned_consumer_bit_mask_ & consumer_state_mask,
@@ -597,6 +558,13 @@
            __FUNCTION__, consumer_state_mask);
   orphaned_consumer_bit_mask_ |= consumer_state_mask;
 
+  uint64_t current_buffer_state =
+      buffer_state_->load(std::memory_order_acquire);
+  if (BufferHubDefs::IsClientReleased(current_buffer_state,
+                                      ~orphaned_consumer_bit_mask_)) {
+    SignalAvailable();
+  }
+
   // Atomically clear the fence state bit as an orphaned consumer will never
   // signal a release fence.
   fence_state_->fetch_and(~consumer_state_mask, std::memory_order_release);
diff --git a/services/vr/bufferhubd/tests/Android.bp b/services/vr/bufferhubd/tests/Android.bp
deleted file mode 100644
index a611268..0000000
--- a/services/vr/bufferhubd/tests/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-cc_test {
-    name: "buffer_hub_binder_service-test",
-    srcs: ["buffer_hub_binder_service-test.cpp"],
-    cflags: [
-        "-DLOG_TAG=\"buffer_hub_binder_service-test\"",
-        "-DTRACE=0",
-        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
-    ],
-    header_libs: ["libdvr_headers"],
-    static_libs: [
-        "libbufferhub",
-        "libbufferhubd",
-        "libgmock",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "liblog",
-        "libpdx_default_transport",
-        "libui",
-        "libutils",
-    ],
-
-    // TODO(b/117568153): Temporarily opt out using libcrt.
-    no_libcrt: true,
-}
diff --git a/services/vr/bufferhubd/tests/buffer_hub_binder_service-test.cpp b/services/vr/bufferhubd/tests/buffer_hub_binder_service-test.cpp
deleted file mode 100644
index 94b422a..0000000
--- a/services/vr/bufferhubd/tests/buffer_hub_binder_service-test.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <binder/IServiceManager.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <private/dvr/IBufferClient.h>
-#include <private/dvr/IBufferHub.h>
-#include <ui/PixelFormat.h>
-
-namespace android {
-namespace dvr {
-
-namespace {
-
-using testing::IsNull;
-using testing::NotNull;
-
-const int kWidth = 640;
-const int kHeight = 480;
-const int kLayerCount = 1;
-const int kFormat = HAL_PIXEL_FORMAT_RGBA_8888;
-const int kUsage = 0;
-const size_t kUserMetadataSize = 0;
-
-class BufferHubBinderServiceTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    status_t ret = getService<IBufferHub>(
-        String16(IBufferHub::getServiceName()), &service);
-    ASSERT_EQ(ret, OK);
-    ASSERT_THAT(service, NotNull());
-  }
-
-  sp<IBufferHub> service;
-};
-
-TEST_F(BufferHubBinderServiceTest, TestCreateBuffer) {
-  sp<IBufferClient> bufferClient = service->createBuffer(
-      kWidth, kHeight, kLayerCount, kFormat, kUsage, kUserMetadataSize);
-  ASSERT_THAT(bufferClient, NotNull());
-  EXPECT_TRUE(bufferClient->isValid());
-}
-
-TEST_F(BufferHubBinderServiceTest, TestDuplicateAndImportBuffer) {
-  sp<IBufferClient> bufferClient = service->createBuffer(
-      kWidth, kHeight, kLayerCount, kFormat, kUsage, kUserMetadataSize);
-  ASSERT_THAT(bufferClient, NotNull());
-  EXPECT_TRUE(bufferClient->isValid());
-
-  uint64_t token1 = 0ULL;
-  status_t ret = bufferClient->duplicate(&token1);
-  EXPECT_EQ(ret, OK);
-
-  // Tokens should be unique even corresponding to the same buffer
-  uint64_t token2 = 0ULL;
-  ret = bufferClient->duplicate(&token2);
-  EXPECT_EQ(ret, OK);
-  EXPECT_NE(token2, token1);
-
-  sp<IBufferClient> bufferClient1;
-  ret = service->importBuffer(token1, &bufferClient1);
-  EXPECT_EQ(ret, OK);
-  ASSERT_THAT(bufferClient1, NotNull());
-  EXPECT_TRUE(bufferClient1->isValid());
-
-  // Consumes the token to keep the service clean
-  sp<IBufferClient> bufferClient2;
-  ret = service->importBuffer(token2, &bufferClient2);
-  EXPECT_EQ(ret, OK);
-  ASSERT_THAT(bufferClient2, NotNull());
-  EXPECT_TRUE(bufferClient2->isValid());
-}
-
-TEST_F(BufferHubBinderServiceTest, TestImportUnexistingToken) {
-  // There is very little chance that this test fails if there is a token = 0
-  // in the service.
-  uint64_t unexistingToken = 0ULL;
-  sp<IBufferClient> bufferClient;
-  status_t ret = service->importBuffer(unexistingToken, &bufferClient);
-  EXPECT_EQ(ret, PERMISSION_DENIED);
-  EXPECT_THAT(bufferClient, IsNull());
-}
-
-}  // namespace
-
-}  // namespace dvr
-}  // namespace android
\ No newline at end of file