Merge "Temporarily turn off latency reporting" into sc-dev
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 712adfa..1fec080 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -101,7 +101,7 @@
 };
 
 enum class InputDeviceLightType : int32_t {
-    SINGLE = 0,
+    MONO = 0,
     PLAYER_ID = 1,
     RGB = 2,
     MULTI_COLOR = 3,
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index ef7fd44..445df9e 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -489,14 +489,16 @@
 
 bool IPCThreadState::flushIfNeeded()
 {
-    if (mIsLooper || mServingStackPointer != nullptr) {
+    if (mIsLooper || mServingStackPointer != nullptr || mIsFlushing) {
         return false;
     }
+    mIsFlushing = true;
     // In case this thread is not a looper and is not currently serving a binder transaction,
     // there's no guarantee that this thread will call back into the kernel driver any time
     // soon. Therefore, flush pending commands such as BC_FREE_BUFFER, to prevent them from getting
     // stuck in this thread's out buffer.
     flushCommands();
+    mIsFlushing = false;
     return true;
 }
 
@@ -612,12 +614,6 @@
     mPostWriteStrongDerefs.clear();
 }
 
-void IPCThreadState::createTransactionReference(RefBase* ref)
-{
-    ref->incStrong(mProcess.get());
-    mPostWriteStrongDerefs.push(ref);
-}
-
 void IPCThreadState::joinThreadPool(bool isMain)
 {
     LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
@@ -847,15 +843,15 @@
 }
 
 IPCThreadState::IPCThreadState()
-    : mProcess(ProcessState::self()),
-      mServingStackPointer(nullptr),
-      mWorkSource(kUnsetWorkSource),
-      mPropagateWorkSource(false),
-      mIsLooper(false),
-      mStrictModePolicy(0),
-      mLastTransactionBinderFlags(0),
-      mCallRestriction(mProcess->mCallRestriction)
-{
+      : mProcess(ProcessState::self()),
+        mServingStackPointer(nullptr),
+        mWorkSource(kUnsetWorkSource),
+        mPropagateWorkSource(false),
+        mIsLooper(false),
+        mIsFlushing(false),
+        mStrictModePolicy(0),
+        mLastTransactionBinderFlags(0),
+        mCallRestriction(mProcess->mCallRestriction) {
     pthread_setspecific(gTLS, this);
     clearCaller();
     mIn.setDataCapacity(256);
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 4fd0dc7..bade918 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -401,7 +401,7 @@
         uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
         result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
         if (result == -1) {
-            ALOGI("Binder ioctl to enable oneway spam detection failed: %s", strerror(errno));
+            ALOGD("Binder ioctl to enable oneway spam detection failed: %s", strerror(errno));
         }
     } else {
         ALOGW("Opening '%s' failed: %s\n", driver, strerror(errno));
diff --git a/libs/binder/aidl/android/content/pm/OWNERS b/libs/binder/aidl/android/content/pm/OWNERS
index b99ca09..3100518 100644
--- a/libs/binder/aidl/android/content/pm/OWNERS
+++ b/libs/binder/aidl/android/content/pm/OWNERS
@@ -1,4 +1,5 @@
 narayan@google.com
 patb@google.com
 svetoslavganov@google.com
-toddke@google.com
\ No newline at end of file
+toddke@google.com
+patb@google.com
\ No newline at end of file
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 23a0cb0..196a41b 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -162,12 +162,6 @@
             // This constant needs to be kept in sync with Binder.UNSET_WORKSOURCE from the Java
             // side.
             static const int32_t kUnsetWorkSource = -1;
-
-            // Create a temp reference until commands in queue flushed to driver
-            // Internal only.
-            // @internal
-            void                 createTransactionReference(RefBase* ref);
-
 private:
                                 IPCThreadState();
                                 ~IPCThreadState();
@@ -212,6 +206,7 @@
             // Whether the work source should be propagated.
             bool                mPropagateWorkSource;
             bool                mIsLooper;
+            bool mIsFlushing;
             int32_t             mStrictModePolicy;
             int32_t             mLastTransactionBinderFlags;
             CallRestriction     mCallRestriction;
diff --git a/libs/binder/include/binder/ParcelRef.h b/libs/binder/include/binder/ParcelRef.h
deleted file mode 100644
index 497da2d..0000000
--- a/libs/binder/include/binder/ParcelRef.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-
-#include <binder/Parcel.h>
-#include <utils/RefBase.h>
-
-// ---------------------------------------------------------------------------
-namespace android {
-
-/**
- * internal use only
- * @internal
- */
-class ParcelRef : public Parcel, public RefBase
-{
-public:
-    static sp<ParcelRef> create() {
-        return new ParcelRef();
-    }
-
-private:
-    ParcelRef() = default;
-};
-
-} // namespace android
-
-// ---------------------------------------------------------------------------
\ No newline at end of file
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 0c3fbcd..5612d1d 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -30,7 +30,6 @@
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-#include <binder/ParcelRef.h>
 
 #include <linux/sched.h>
 #include <sys/epoll.h>
@@ -891,36 +890,6 @@
     }
 }
 
-TEST_F(BinderLibTest, ParcelAllocatedOnAnotherThread) {
-    sp<IBinder> server = addServer();
-    ASSERT_TRUE(server != nullptr);
-
-    Parcel data;
-    sp<ParcelRef> reply = ParcelRef::create();
-
-    // when we have a Parcel which is deleted on another thread, if it gets
-    // deleted, it will tell the kernel this, and it will drop strong references
-    // to binder, so that we can't BR_ACQUIRE would fail
-    IPCThreadState::self()->createTransactionReference(reply.get());
-    ASSERT_EQ(NO_ERROR, server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION,
-                                         data,
-                                         reply.get()));
-
-    // we have sp to binder, but it is not actually acquired by kernel, the
-    // transaction is sitting on an out buffer
-    sp<IBinder> binder = reply->readStrongBinder();
-
-    std::thread([&] {
-        // without the transaction reference, this would cause the Parcel to be
-        // deallocated before the first thread flushes BR_ACQUIRE
-        reply = nullptr;
-        IPCThreadState::self()->flushCommands();
-    }).join();
-
-    ASSERT_NE(nullptr, binder);
-    ASSERT_EQ(NO_ERROR, binder->pingBinder());
-}
-
 TEST_F(BinderLibTest, CheckNoHeaderMappedInUser) {
     Parcel data, reply;
     sp<BinderLibTestCallBack> callBack = new BinderLibTestCallBack();
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 53721cf..71e18a9 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -824,6 +824,36 @@
         return error;
     }
 
+    virtual status_t addTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) {
+        Parcel data, reply;
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
+
+        const status_t error =
+                remote()->transact(BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER, data,
+                                   &reply);
+        if (error != NO_ERROR) {
+            ALOGE("addTunnelModeEnabledListener: Failed to transact");
+        }
+        return error;
+    }
+
+    virtual status_t removeTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) {
+        Parcel data, reply;
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
+
+        const status_t error =
+                remote()->transact(BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER, data,
+                                   &reply);
+        if (error != NO_ERROR) {
+            ALOGE("removeTunnelModeEnabledListener: Failed to transact");
+        }
+        return error;
+    }
+
     status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                         ui::DisplayModeId defaultMode, bool allowGroupSwitching,
                                         float primaryRefreshRateMin, float primaryRefreshRateMax,
@@ -1740,6 +1770,26 @@
             }
             return removeFpsListener(listener);
         }
+        case ADD_TUNNEL_MODE_ENABLED_LISTENER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<gui::ITunnelModeEnabledListener> listener;
+            status_t result = data.readNullableStrongBinder(&listener);
+            if (result != NO_ERROR) {
+                ALOGE("addTunnelModeEnabledListener: Failed to read listener");
+                return result;
+            }
+            return addTunnelModeEnabledListener(listener);
+        }
+        case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<gui::ITunnelModeEnabledListener> listener;
+            status_t result = data.readNullableStrongBinder(&listener);
+            if (result != NO_ERROR) {
+                ALOGE("removeTunnelModeEnabledListener: Failed to read listener");
+                return result;
+            }
+            return removeTunnelModeEnabledListener(listener);
+        }
         case SET_DESIRED_DISPLAY_MODE_SPECS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> displayToken = data.readStrongBinder();
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index d27d1ec..e117d11 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1268,8 +1268,11 @@
                 if (err == NO_ERROR) {
                     return NO_ERROR;
                 }
-                if (composerService()->authenticateSurfaceTexture(
-                        mGraphicBufferProducer)) {
+                sp<ISurfaceComposer> surfaceComposer = composerService();
+                if (surfaceComposer == nullptr) {
+                    return -EPERM; // likely permissions error
+                }
+                if (surfaceComposer->authenticateSurfaceTexture(mGraphicBufferProducer)) {
                     *value = 1;
                 } else {
                     *value = 0;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index aa93808..371454a 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -65,12 +65,12 @@
     connectLocked();
 }
 
-void ComposerService::connectLocked() {
+bool ComposerService::connectLocked() {
     const String16 name("SurfaceFlinger");
-    while (getService(name, &mComposerService) != NO_ERROR) {
-        usleep(250000);
+    mComposerService = waitForService<ISurfaceComposer>(name);
+    if (mComposerService == nullptr) {
+        return false; // fatal error or permission problem
     }
-    assert(mComposerService != nullptr);
 
     // Create the death listener.
     class DeathObserver : public IBinder::DeathRecipient {
@@ -86,15 +86,16 @@
 
     mDeathObserver = new DeathObserver(*const_cast<ComposerService*>(this));
     IInterface::asBinder(mComposerService)->linkToDeath(mDeathObserver);
+    return true;
 }
 
 /*static*/ sp<ISurfaceComposer> ComposerService::getComposerService() {
     ComposerService& instance = ComposerService::getInstance();
     Mutex::Autolock _l(instance.mLock);
     if (instance.mComposerService == nullptr) {
-        ComposerService::getInstance().connectLocked();
-        assert(instance.mComposerService != nullptr);
-        ALOGD("ComposerService reconnected");
+        if (ComposerService::getInstance().connectLocked()) {
+            ALOGD("ComposerService reconnected");
+        }
     }
     return instance.mComposerService;
 }
@@ -2077,6 +2078,16 @@
     return ComposerService::getComposerService()->removeFpsListener(listener);
 }
 
+status_t SurfaceComposerClient::addTunnelModeEnabledListener(
+        const sp<gui::ITunnelModeEnabledListener>& listener) {
+    return ComposerService::getComposerService()->addTunnelModeEnabledListener(listener);
+}
+
+status_t SurfaceComposerClient::removeTunnelModeEnabledListener(
+        const sp<gui::ITunnelModeEnabledListener>& listener) {
+    return ComposerService::getComposerService()->removeTunnelModeEnabledListener(listener);
+}
+
 bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp<IBinder>& displayToken) {
     bool support = false;
     ComposerService::getComposerService()->getDisplayBrightnessSupport(displayToken, &support);
diff --git a/libs/gui/aidl/android/gui/ITunnelModeEnabledListener.aidl b/libs/gui/aidl/android/gui/ITunnelModeEnabledListener.aidl
new file mode 100644
index 0000000..2a89cca
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ITunnelModeEnabledListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+/** @hide */
+oneway interface ITunnelModeEnabledListener {
+
+    /**
+     * Called when tunnel mode status has changed. Tunnel mode is:
+     *  - enabled when there is a sideband stream attached to one of the layers in
+     *    surface flinger
+     *  - disabled when there is no layer with a sideband stream
+     */
+    void onTunnelModeEnabledChanged(boolean enabled);
+}
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index cb04689..439d90a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -21,6 +21,7 @@
 #include <android/gui/IHdrLayerInfoListener.h>
 #include <android/gui/IScreenCaptureListener.h>
 #include <android/gui/ITransactionTraceListener.h>
+#include <android/gui/ITunnelModeEnabledListener.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
 #include <gui/FrameTimelineInfo.h>
@@ -377,6 +378,21 @@
      */
     virtual status_t removeFpsListener(const sp<gui::IFpsListener>& listener) = 0;
 
+    /* Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger.
+     *
+     * Requires ACCESS_SURFACE_FLINGER permission.
+     */
+    virtual status_t addTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) = 0;
+
+    /*
+     * Removes a listener that was receiving tunnel mode enabled updates from SurfaceFlinger.
+     *
+     * Requires ACCESS_SURFACE_FLINGER permission.
+     */
+    virtual status_t removeTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) = 0;
+
     /* Sets the refresh rate boundaries for the display.
      *
      * The primary refresh rate range represents display manager's general guidance on the display
@@ -607,6 +623,8 @@
         ADD_HDR_LAYER_INFO_LISTENER,
         REMOVE_HDR_LAYER_INFO_LISTENER,
         ON_PULL_ATOM,
+        ADD_TUNNEL_MODE_ENABLED_LISTENER,
+        REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0940e9d..2582882 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -50,6 +50,7 @@
 class ISurfaceComposerClient;
 class IGraphicBufferProducer;
 class IRegionSamplingListener;
+class ITunnelModeEnabledListener;
 class Region;
 
 struct SurfaceControlStats {
@@ -610,6 +611,10 @@
     static status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener);
     static status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener);
     static status_t removeFpsListener(const sp<gui::IFpsListener>& listener);
+    static status_t addTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener);
+    static status_t removeTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener);
 
 private:
     virtual void onFirstRef();
diff --git a/services/surfaceflinger/TraceUtils.h b/libs/gui/include/gui/TraceUtils.h
similarity index 89%
rename from services/surfaceflinger/TraceUtils.h
rename to libs/gui/include/gui/TraceUtils.h
index 90a34a5..b9ec14a 100644
--- a/services/surfaceflinger/TraceUtils.h
+++ b/libs/gui/include/gui/TraceUtils.h
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/183120308): This file is a copy of f/b/libs/hwui/utils/TraceUtils.h
-//  It should be migrated to a common place where both SF and hwui could use it.
-
 #pragma once
 
 #include <cutils/trace.h>
diff --git a/libs/gui/include/private/gui/ComposerService.h b/libs/gui/include/private/gui/ComposerService.h
index 50bd742..fa1071a 100644
--- a/libs/gui/include/private/gui/ComposerService.h
+++ b/libs/gui/include/private/gui/ComposerService.h
@@ -45,13 +45,12 @@
     Mutex mLock;
 
     ComposerService();
-    void connectLocked();
+    bool connectLocked();
     void composerServiceDied();
     friend class Singleton<ComposerService>;
 public:
-
     // Get a connection to the Composer Service.  This will block until
-    // a connection is established.
+    // a connection is established. Returns null if permission is denied.
     static sp<ISurfaceComposer> getComposerService();
 };
 
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index ea8c295..b8d34c3 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -845,6 +845,16 @@
         return NO_ERROR;
     }
     status_t removeFpsListener(const sp<gui::IFpsListener>& /*listener*/) { return NO_ERROR; }
+
+    status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& /*listener*/) {
+        return NO_ERROR;
+    }
+
+    status_t removeTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& /*listener*/) {
+        return NO_ERROR;
+    }
+
     status_t setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
                                         ui::DisplayModeId /*defaultMode*/,
                                         bool /*allowGroupSwitching*/,
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 649a140..70ed438 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -39,6 +39,60 @@
 
 namespace android {
 
+namespace {
+
+float transformAngle(const ui::Transform& transform, float angleRadians) {
+    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+    // Coordinate system: down is increasing Y, right is increasing X.
+    float x = sinf(angleRadians);
+    float y = -cosf(angleRadians);
+    vec2 transformedPoint = transform.transform(x, y);
+
+    // Determine how the origin is transformed by the matrix so that we
+    // can transform orientation vectors.
+    const vec2 origin = transform.transform(0, 0);
+
+    transformedPoint.x -= origin.x;
+    transformedPoint.y -= origin.y;
+
+    // Derive the transformed vector's clockwise angle from vertical.
+    float result = atan2f(transformedPoint.x, -transformedPoint.y);
+    if (result < -M_PI_2) {
+        result += M_PI;
+    } else if (result > M_PI_2) {
+        result -= M_PI;
+    }
+    return result;
+}
+
+// Rotates the given point to the transform's orientation. If the display width and height are
+// provided, the point is rotated in the screen space. Otherwise, the point is rotated about the
+// origin. This helper is used to avoid the extra overhead of creating new Transforms.
+vec2 rotatePoint(const ui::Transform& transform, float x, float y, int32_t displayWidth = 0,
+                 int32_t displayHeight = 0) {
+    // 0x7 encapsulates all 3 rotations (see ui::Transform::RotationFlags)
+    static const int ALL_ROTATIONS_MASK = 0x7;
+    const uint32_t orientation = (transform.getOrientation() & ALL_ROTATIONS_MASK);
+    if (orientation == ui::Transform::ROT_0) {
+        return {x, y};
+    }
+
+    vec2 xy(x, y);
+    if (orientation == ui::Transform::ROT_90) {
+        xy.x = displayHeight - y;
+        xy.y = x;
+    } else if (orientation == ui::Transform::ROT_180) {
+        xy.x = displayWidth - x;
+        xy.y = displayHeight - y;
+    } else if (orientation == ui::Transform::ROT_270) {
+        xy.x = y;
+        xy.y = displayWidth - x;
+    }
+    return xy;
+}
+
+} // namespace
+
 const char* motionClassificationToString(MotionClassification classification) {
     switch (classification) {
         case MotionClassification::NONE:
@@ -315,9 +369,14 @@
 }
 
 void PointerCoords::transform(const ui::Transform& transform) {
-    vec2 newCoords = transform.transform(getX(), getY());
-    setAxisValue(AMOTION_EVENT_AXIS_X, newCoords.x);
-    setAxisValue(AMOTION_EVENT_AXIS_Y, newCoords.y);
+    const vec2 xy = transform.transform(getXYValue());
+    setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+    setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+    if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_ORIENTATION)) {
+        const float val = getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(transform, val));
+    }
 }
 
 // --- PointerProperties ---
@@ -444,45 +503,32 @@
 }
 
 float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
-        size_t historicalIndex) const {
-    if (axis != AMOTION_EVENT_AXIS_X && axis != AMOTION_EVENT_AXIS_Y) {
-        return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
-    }
-    // 0x7 encapsulates all 3 rotations (see ui::Transform::RotationFlags)
-    static const int ALL_ROTATIONS_MASK = 0x7;
-    uint32_t orientation = (mTransform.getOrientation() & ALL_ROTATIONS_MASK);
-    if (orientation == ui::Transform::ROT_0) {
-        return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+                                             size_t historicalIndex) const {
+    const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
+
+    if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
+        // For compatibility, convert raw coordinates into "oriented screen space". Once app
+        // developers are educated about getRaw, we can consider removing this.
+        const vec2 xy = rotatePoint(mTransform, coords->getX(), coords->getY(), mDisplayWidth,
+                                    mDisplayHeight);
+        static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
+        return xy[axis];
     }
 
-    // For compatibility, convert raw coordinates into "oriented screen space". Once app developers
-    // are educated about getRaw, we can consider removing this.
-    vec2 xy = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getXYValue();
-    const float unrotatedX = xy.x;
-    if (orientation == ui::Transform::ROT_90) {
-        xy.x = mDisplayHeight - xy.y;
-        xy.y = unrotatedX;
-    } else if (orientation == ui::Transform::ROT_180) {
-        xy.x = mDisplayWidth - xy.x;
-        xy.y = mDisplayHeight - xy.y;
-    } else if (orientation == ui::Transform::ROT_270) {
-        xy.x = xy.y;
-        xy.y = mDisplayWidth - unrotatedX;
-    }
-    static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
-    return xy[axis];
+    return coords->getAxisValue(axis);
 }
 
 float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
         size_t historicalIndex) const {
-    if (axis != AMOTION_EVENT_AXIS_X && axis != AMOTION_EVENT_AXIS_Y) {
-        return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+    const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
+
+    if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
+        const vec2 xy = mTransform.transform(coords->getXYValue());
+        static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
+        return xy[axis];
     }
 
-    vec2 vals = mTransform.transform(
-            getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getXYValue());
-    static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
-    return vals[axis];
+    return coords->getAxisValue(axis);
 }
 
 ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
@@ -513,78 +559,30 @@
     }
 }
 
-static vec2 transformPoint(const std::array<float, 9>& matrix, float x, float y) {
-    // Apply perspective transform like Skia.
-    float newX = matrix[0] * x + matrix[1] * y + matrix[2];
-    float newY = matrix[3] * x + matrix[4] * y + matrix[5];
-    float newZ = matrix[6] * x + matrix[7] * y + matrix[8];
-    if (newZ) {
-        newZ = 1.0f / newZ;
-    }
-    vec2 transformedPoint;
-    transformedPoint.x = newX * newZ;
-    transformedPoint.y = newY * newZ;
-    return transformedPoint;
-}
-
-static float transformAngle(const std::array<float, 9>& matrix, float angleRadians, float originX,
-                            float originY) {
-    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
-    // Coordinate system: down is increasing Y, right is increasing X.
-    float x = sinf(angleRadians);
-    float y = -cosf(angleRadians);
-    vec2 transformedPoint = transformPoint(matrix, x, y);
-
-    transformedPoint.x -= originX;
-    transformedPoint.y -= originY;
-
-    // Derive the transformed vector's clockwise angle from vertical.
-    float result = atan2f(transformedPoint.x, -transformedPoint.y);
-    if (result < - M_PI_2) {
-        result += M_PI;
-    } else if (result > M_PI_2) {
-        result -= M_PI;
-    }
-    return result;
-}
-
 void MotionEvent::transform(const std::array<float, 9>& matrix) {
-    // We want to preserve the rawX and rawY so we just update the transform
-    // using the values of the transform passed in
+    // We want to preserve the raw axes values stored in the PointerCoords, so we just update the
+    // transform using the values passed in.
     ui::Transform newTransform;
     newTransform.set(matrix);
     mTransform = newTransform * mTransform;
 
-    // Determine how the origin is transformed by the matrix so that we
-    // can transform orientation vectors.
-    vec2 origin = transformPoint(matrix, 0, 0);
-
-    // Apply the transformation to all samples.
-    size_t numSamples = mSamplePointerCoords.size();
-    for (size_t i = 0; i < numSamples; i++) {
-        PointerCoords& c = mSamplePointerCoords.editItemAt(i);
-        float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                       transformAngle(matrix, orientation, origin.x, origin.y));
-    }
+    // We need to update the AXIS_ORIENTATION value here to maintain the old behavior where the
+    // orientation angle is not affected by the initial transformation set in the MotionEvent.
+    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(),
+                  [&newTransform](PointerCoords& c) {
+                      float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+                      c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                                     transformAngle(newTransform, orientation));
+                  });
 }
 
 void MotionEvent::applyTransform(const std::array<float, 9>& matrix) {
-    // Determine how the origin is transformed by the matrix so that we
-    // can transform orientation vectors.
-    vec2 origin = transformPoint(matrix, 0, 0);
+    ui::Transform transform;
+    transform.set(matrix);
 
     // Apply the transformation to all samples.
-    size_t numSamples = mSamplePointerCoords.size();
-    for (size_t i = 0; i < numSamples; i++) {
-        PointerCoords& c = mSamplePointerCoords.editItemAt(i);
-        float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                       transformAngle(matrix, orientation, origin.x, origin.y));
-        vec2 xy = transformPoint(matrix, c.getX(), c.getY());
-        c.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
-        c.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
-    }
+    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(),
+                  [&transform](PointerCoords& c) { c.transform(transform); });
 }
 
 #ifdef __linux__
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 63ed1ba..715f3f8 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -107,6 +107,9 @@
  * material design guidelines.
  */
 struct ShadowSettings {
+    // Boundaries of the shadow.
+    FloatRect boundaries = FloatRect();
+
     // Color to the ambient shadow. The alpha is premultiplied.
     vec4 ambientColor = vec4();
 
@@ -150,6 +153,10 @@
     // True if blending will be forced to be disabled.
     bool disableBlending = false;
 
+    // If true, then this layer casts a shadow and/or blurs behind it, but it does
+    // not otherwise draw any of the layer's other contents.
+    bool skipContentDraw = false;
+
     ShadowSettings shadow;
 
     int backgroundBlurRadius = 0;
@@ -189,9 +196,10 @@
 }
 
 static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
-    return lhs.ambientColor == rhs.ambientColor && lhs.spotColor == rhs.spotColor &&
-            lhs.lightPos == rhs.lightPos && lhs.lightRadius == rhs.lightRadius &&
-            lhs.length == rhs.length && lhs.casterIsTranslucent == rhs.casterIsTranslucent;
+    return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
+            lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
+            lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
+            lhs.casterIsTranslucent == rhs.casterIsTranslucent;
 }
 
 static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
@@ -208,7 +216,8 @@
     return lhs.geometry == rhs.geometry && lhs.source == rhs.source && lhs.alpha == rhs.alpha &&
             lhs.sourceDataspace == rhs.sourceDataspace &&
             lhs.colorTransform == rhs.colorTransform &&
-            lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow &&
+            lhs.disableBlending == rhs.disableBlending &&
+            lhs.skipContentDraw == rhs.skipContentDraw && lhs.shadow == rhs.shadow &&
             lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
             lhs.blurRegionTransform == rhs.blurRegionTransform &&
             lhs.stretchEffect == rhs.stretchEffect;
@@ -251,6 +260,8 @@
 
 static inline void PrintTo(const ShadowSettings& settings, ::std::ostream* os) {
     *os << "ShadowSettings {";
+    *os << "\n    .boundaries = ";
+    PrintTo(settings.boundaries, os);
     *os << "\n    .ambientColor = " << settings.ambientColor;
     *os << "\n    .spotColor = " << settings.spotColor;
     *os << "\n    .lightPos = " << settings.lightPos;
@@ -286,6 +297,7 @@
     PrintTo(settings.sourceDataspace, os);
     *os << "\n    .colorTransform = " << settings.colorTransform;
     *os << "\n    .disableBlending = " << settings.disableBlending;
+    *os << "\n    .skipContentDraw = " << settings.skipContentDraw;
     *os << "\n    .backgroundBlurRadius = " << settings.backgroundBlurRadius;
     for (auto blurRegion : settings.blurRegions) {
         *os << "\n";
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index ddaa7c7..d1bbcc5 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -43,6 +43,11 @@
  */
 #define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS "debug.renderengine.capture_skia_ms"
 
+/**
+ * Set to the most recently saved file once the capture is finished.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME "debug.renderengine.capture_filename"
+
 struct ANativeWindowBuffer;
 
 namespace android {
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 77e01f4..61c297c 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -37,6 +37,10 @@
                                      0.f,  0.7f, 0.f, 0.f,
                                      0.f,   0.f, 1.f, 0.f,
                                    67.3f, 52.2f, 0.f, 1.f);
+const auto kScaleAsymmetric = mat4(0.8f, 0.f,  0.f, 0.f,
+                                   0.f,  1.1f, 0.f, 0.f,
+                                   0.f,  0.f,  1.f, 0.f,
+                                   0.f,  0.f,  0.f, 1.f);
 // clang-format on
 // When setting layer.sourceDataspace, whether it matches the destination or not determines whether
 // a color correction effect is added to the shader.
@@ -77,12 +81,7 @@
     // This matrix, which has different scales for x and y, will
     // generate the slower (more general case) version, which has variants for translucent
     // casters and rounded rects.
-    // clang-format off
-    layer.geometry.positionTransform = mat4(0.7f, 0.f,  0.f, 0.f,
-                                            0.f, 0.8f, 0.f, 0.f,
-                                            0.f, 0.f,  1.f, 0.f,
-                                            0.f, 0.f,  0.f, 1.f);
-    // clang-format on
+    layer.geometry.positionTransform = kScaleAsymmetric;
     for (auto translucent : {false, true}) {
         layer.shadow.casterIsTranslucent = translucent;
         renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
@@ -150,7 +149,6 @@
                     PixelSource{
                             .solidColor = half3(0.1f, 0.2f, 0.3f),
                     },
-            .alpha = 1,
     };
 
     auto layers = std::vector<const LayerSettings*>{&layer};
@@ -186,23 +184,26 @@
 
 // The unique feature of these layers is that the boundary is slightly smaller than the rounded
 // rect crop, so the rounded edges intersect that boundary and require a different clipping method.
+// For buffers, this is done with a stage that computes coverage and it will differ for round and
+// elliptical corners.
 static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
                               const std::shared_ptr<ExternalTexture>& dstTexture,
                               const std::shared_ptr<ExternalTexture>& srcTexture) {
     const Rect& displayRect = display.physicalDisplay;
     FloatRect rect(0, 0, displayRect.width(), displayRect.height() - 20); // boundary is smaller
 
-    // clang-format off
-    const auto symmetric = mat4(0.9f, 0.f,  0.f, 0.f,
-                                0.f,  0.9f, 0.f, 0.f,
-                                0.f,  0.f,  1.f, 0.f,
-                                8.8f, 8.1f, 0.f, 1.f);
-    const auto asymmetric = mat4(0.9f, 0.f,  0.f, 0.f,
-                                 0.f,  0.7f, 0.f, 0.f,
-                                 0.f,  0.f,  1.f, 0.f,
-                                 8.8f, 8.1f, 0.f, 1.f);
+    PixelSource bufferSource{.buffer = Buffer{
+                                     .buffer = srcTexture,
+                                     .isOpaque = 0,
+                                     .maxLuminanceNits = 1000.f,
+                             }};
+    PixelSource bufferOpaque{.buffer = Buffer{
+                                     .buffer = srcTexture,
+                                     .isOpaque = 1,
+                                     .maxLuminanceNits = 1000.f,
+                             }};
+    PixelSource colorSource{.solidColor = half3(0.1f, 0.2f, 0.3f)};
 
-    // clang-format on
     LayerSettings layer{
             .geometry =
                     Geometry{
@@ -211,23 +212,24 @@
                             .roundedCornersCrop =
                                     FloatRect(0, 0, displayRect.width(), displayRect.height()),
                     },
-            .source = PixelSource{.buffer =
-                                          Buffer{
-                                                  .buffer = srcTexture,
-                                                  .isOpaque = 0,
-                                                  .maxLuminanceNits = 1000.f,
-                                          }},
-            .sourceDataspace = kOtherDataSpace,
     };
 
     auto layers = std::vector<const LayerSettings*>{&layer};
-    for (auto transform : {symmetric, asymmetric}) {
-        layer.geometry.positionTransform = transform;
-        // In real use, I saw alpha of 1.0 and 0.999, probably a mistake, but cache both shaders.
-        for (float alpha : {0.5f, 1.f}) {
-            layer.alpha = alpha,
-            renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                     base::unique_fd(), nullptr);
+    for (auto pixelSource : {bufferSource, bufferOpaque, colorSource}) {
+        layer.source = pixelSource;
+        for (auto dataspace : {kDestDataSpace, kOtherDataSpace}) {
+            layer.sourceDataspace = dataspace;
+            // Produce a CircularRRect clip and an EllipticalRRect clip
+            for (auto transform : {kScaleAndTranslate, kScaleAsymmetric}) {
+                layer.geometry.positionTransform = transform;
+                // In real use, I saw alpha of 1.0 and 0.999, probably a mistake, but cache both
+                // shaders.
+                for (float alpha : {0.5f, 1.f}) {
+                    layer.alpha = alpha,
+                    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
+                                             base::unique_fd(), nullptr);
+                }
+            }
         }
     }
 }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 6cc3d37..47c330f 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -505,10 +505,11 @@
     if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) {
         return;
     }
-    // we currently don't attempt to map a buffer if the buffer contains protected content
-    // because GPU resources for protected buffers is much more limited.
+    // We currently don't attempt to map a buffer if the buffer contains protected content
+    // or we are using a protected context because GPU resources for protected buffers is
+    // much more limited.
     const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer) {
+    if (isProtectedBuffer || mInProtectedContext) {
         return;
     }
     ATRACE_CALL();
@@ -517,7 +518,7 @@
     // bound context if we are not already using the protected context (and subsequently switch
     // back after the buffer is cached).
     auto grContext = getActiveGrContext();
-    auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
+    auto& cache = mTextureCache;
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     mGraphicBufferExternalRefs[buffer->getId()]++;
@@ -724,6 +725,14 @@
         return BAD_VALUE;
     }
 
+    // setup color filter if necessary
+    sk_sp<SkColorFilter> displayColorTransform;
+    if (display.colorTransform != mat4()) {
+        displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
+    }
+    const bool ctModifiesAlpha =
+            displayColorTransform && !displayColorTransform->isAlphaUnchanged();
+
     // Find if any layers have requested blur, we'll use that info to decide when to render to an
     // offscreen buffer and when to render to the native buffer.
     sk_sp<SkSurface> activeSurface(dstSurface);
@@ -733,6 +742,10 @@
     if (mBlurFilter) {
         bool requiresCompositionLayer = false;
         for (const auto& layer : layers) {
+            // if the layer doesn't have blur or it is not visible then continue
+            if (!layerHasBlur(layer, ctModifiesAlpha)) {
+                continue;
+            }
             if (layer->backgroundBlurRadius > 0 &&
                 layer->backgroundBlurRadius < BlurFilter::kMaxCrossFadeRadius) {
                 requiresCompositionLayer = true;
@@ -778,12 +791,6 @@
         canvas->drawRegion(clearRegion, paint);
     }
 
-    // setup color filter if necessary
-    sk_sp<SkColorFilter> displayColorTransform;
-    if (display.colorTransform != mat4()) {
-        displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
-    }
-
     for (const auto& layer : layers) {
         ATRACE_NAME("DrawLayer");
 
@@ -846,8 +853,10 @@
         // Layers have a local transform that should be applied to them
         canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
 
-        const auto [bounds, roundRectClip] = getBoundsAndClip(layer);
-        if (mBlurFilter && layerHasBlur(layer)) {
+        const auto [bounds, roundRectClip] =
+                getBoundsAndClip(layer->geometry.boundaries, layer->geometry.roundedCornersCrop,
+                                 layer->geometry.roundedCornersRadius);
+        if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
             // if multiple layers have blur, then we need to take a snapshot now because
@@ -895,14 +904,21 @@
             }
         }
 
-        // Shadows are assumed to live only on their own layer - it's not valid
-        // to draw the boundary rectangles when there is already a caster shadow
-        // TODO(b/175915334): consider relaxing this restriction to enable more flexible
-        // composition - using a well-defined invalid color is long-term less error-prone.
         if (layer->shadow.length > 0) {
             // This would require a new parameter/flag to SkShadowUtils::DrawShadow
             LOG_ALWAYS_FATAL_IF(layer->disableBlending, "Cannot disableBlending with a shadow");
 
+            SkRRect shadowBounds, shadowClip;
+            if (layer->geometry.boundaries == layer->shadow.boundaries) {
+                shadowBounds = bounds;
+                shadowClip = roundRectClip;
+            } else {
+                std::tie(shadowBounds, shadowClip) =
+                        getBoundsAndClip(layer->shadow.boundaries,
+                                         layer->geometry.roundedCornersCrop,
+                                         layer->geometry.roundedCornersRadius);
+            }
+
             // Technically, if bounds is a rect and roundRectClip is not empty,
             // it means that the bounds and roundedCornersCrop were different
             // enough that we should intersect them to find the proper shadow.
@@ -910,9 +926,8 @@
             // not match due to rounding errors. Draw the rounded version, which
             // looks more like the intent.
             const auto& rrect =
-                    bounds.isRect() && !roundRectClip.isEmpty() ? roundRectClip : bounds;
+                    shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds;
             drawShadow(canvas, rrect, layer->shadow);
-            continue;
         }
 
         const bool requiresLinearEffect = layer->colorTransform != mat4() ||
@@ -922,8 +937,9 @@
                  display.sdrWhitePointNits != display.maxLuminance);
 
         // quick abort from drawing the remaining portion of the layer
-        if (layer->alpha == 0 && !requiresLinearEffect && !layer->disableBlending &&
-            (!displayColorTransform || displayColorTransform->isAlphaUnchanged())) {
+        if (layer->skipContentDraw ||
+            (layer->alpha == 0 && !requiresLinearEffect && !layer->disableBlending &&
+             (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) {
             continue;
         }
 
@@ -1093,11 +1109,11 @@
     return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
 }
 
-inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(
-        const LayerSettings* layer) {
-    const auto bounds = getSkRect(layer->geometry.boundaries);
-    const auto crop = getSkRect(layer->geometry.roundedCornersCrop);
-    const auto cornerRadius = layer->geometry.roundedCornersRadius;
+inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
+                                                                        const FloatRect& cropRect,
+                                                                        const float cornerRadius) {
+    const SkRect bounds = getSkRect(boundsRect);
+    const SkRect crop = getSkRect(cropRect);
 
     SkRRect clip;
     if (cornerRadius > 0) {
@@ -1178,8 +1194,15 @@
     return {SkRRect::MakeRect(bounds), clip};
 }
 
-inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings* layer) {
-    return layer->backgroundBlurRadius > 0 || layer->blurRegions.size();
+inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings* layer,
+                                             bool colorTransformModifiesAlpha) {
+    if (layer->backgroundBlurRadius > 0 || layer->blurRegions.size()) {
+        // return false if the content is opaque and would therefore occlude the blur
+        const bool opaqueContent = !layer->source.buffer.buffer || layer->source.buffer.isOpaque;
+        const bool opaqueAlpha = layer->alpha == 1.0f && !colorTransformModifiesAlpha;
+        return layer->skipContentDraw || !(opaqueContent && opaqueAlpha);
+    }
+    return false;
 }
 
 inline SkColor SkiaGLRenderEngine::getSkColor(const vec4& color) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 1f528b7..97d3b72 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -88,8 +88,9 @@
                                                          int hwcFormat, Protection protection);
     inline SkRect getSkRect(const FloatRect& layer);
     inline SkRect getSkRect(const Rect& layer);
-    inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const LayerSettings* layer);
-    inline bool layerHasBlur(const LayerSettings* layer);
+    inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds,
+                                                        const FloatRect& crop, float cornerRadius);
+    inline bool layerHasBlur(const LayerSettings* layer, bool colorTransformModifiesAlpha);
     inline SkColor getSkColor(const vec4& color);
     inline SkM44 getSkM44(const mat4& matrix);
     inline SkPoint3 getSkPoint3(const vec3& vector);
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 40f5cf2..856fff4 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -34,7 +34,7 @@
 namespace skia {
 
 // The root of the filename to write a recorded SKP to. In order for this file to
-// be written to /data/user/, user must run 'adb shell setenforce 0' in the device.
+// be written to /data/user/, user must run 'adb shell setenforce 0' on the device.
 static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
 
 SkiaCapture::~SkiaCapture() {
@@ -152,11 +152,12 @@
     // a smart pointer makes the lambda non-copyable. The lambda is only called
     // once, so this is safe.
     SkFILEWStream* stream = mOpenMultiPicStream.release();
-    CommonPool::post([doc = std::move(mMultiPic), stream] {
+    CommonPool::post([doc = std::move(mMultiPic), stream, name = std::move(mCaptureFile)] {
         ALOGD("Finalizing multi frame SKP");
         doc->close();
         delete stream;
-        ALOGD("Multi frame SKP complete.");
+        ALOGD("Multi frame SKP saved to %s.", name.c_str());
+        base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, name);
     });
     mCaptureRunning = false;
 }
@@ -164,12 +165,14 @@
 bool SkiaCapture::setupMultiFrameCapture() {
     ATRACE_CALL();
     ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
+    base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
+    const std::scoped_lock lock(mMutex);
 
-    std::string captureFile;
     // Attach a timestamp to the file.
-    base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+    mCaptureFile.clear();
+    base::StringAppendF(&mCaptureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
                         std::chrono::steady_clock::now().time_since_epoch().count());
-    auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str());
+    auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.c_str());
     // We own this stream and need to hold it until close() finishes.
     if (stream->isValid()) {
         mOpenMultiPicStream = std::move(stream);
@@ -194,7 +197,7 @@
         mCaptureRunning = true;
         return true;
     } else {
-        ALOGE("Could not open \"%s\" for writing.", captureFile.c_str());
+        ALOGE("Could not open \"%s\" for writing.", mCaptureFile.c_str());
         return false;
     }
 }
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
index 5e18e60..f194629 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.h
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -85,6 +85,8 @@
     // Mutex to ensure that a frame in progress when the timer fires is allowed to run to
     // completion before we write the file to disk.
     std::mutex mMutex;
+
+    std::string mCaptureFile;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/debug/record.sh b/libs/renderengine/skia/debug/record.sh
index 25c8cef..e99b7ae 100755
--- a/libs/renderengine/skia/debug/record.sh
+++ b/libs/renderengine/skia/debug/record.sh
@@ -16,14 +16,22 @@
   # first time use requires these changes
   adb root
   adb shell setenforce 0
-  adb shell setprop debug.renderengine.backend "skiagl"
+  adb shell setprop debug.renderengine.backend "skiaglthreaded"
   adb shell stop
   adb shell start
   exit 1;
 fi
 
-# name of the newest file in /data/user/ before starting
-oldname=$(adb shell ls -cr /data/user/ | head -n 1)
+check_permission() {
+    adb shell getenforce
+}
+
+mode=$(check_permission)
+
+if [ "$mode" != "Permissive" ]; then
+   echo "Cannot write to disk from RenderEngine. run 'record.sh rootandsetup'"
+   exit 5
+fi
 
 # record frames for some number of milliseconds.
 adb shell setprop debug.renderengine.capture_skia_ms $1
@@ -38,26 +46,6 @@
 # the process it is recording.
 # /data/user/re_skiacapture_56204430551705.mskp
 
-# list the files here from newest to oldest, keep only the name of the newest.
-name=$(adb shell ls -cr /data/user/ | head -n 1)
-remote_path=/data/user/$name
-
-if [[ $oldname = $name ]]; then
-  echo "No new file written, probably no RenderEngine activity during recording period."
-  exit 1
-fi
-
-# return the size of a file in bytes
-adb_filesize() {
-    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
-}
-
-mskp_size=$(adb_filesize "/data/user/$name")
-if [[ $mskp_size = "0" ]]; then
-  echo "File opened, but remains empty after recording period + wait. Either there was no RenderEngine activity during recording period, or recording process is still working. Check /data/user/$name manually later."
-  exit 1
-fi
-
 spin() {
     case "$spin" in
          1) printf '\b|';;
@@ -69,38 +57,28 @@
     sleep $1
 }
 
-printf "MSKP captured, Waiting for file serialization to finish.\n"
+local_path=~/Downloads/
 
-local_path=~/Downloads/$name
+get_filename() {
+    adb shell getprop debug.renderengine.capture_filename
+}
 
-# wait for the file size to stop changing
-
-timeout=$(( $(date +%s) + 300))
-last_size='0' # output of last size check command
-unstable=true # false once the file size stops changing
-counter=0 # used to perform size check only 1/sec though we update spinner 20/sec
-# loop until the file size is unchanged for 1 second.
-while [ $unstable != 0 ] ; do
+remote_path=""
+counter=0 # used to check only 1/sec though we update spinner 20/sec
+while [ -z $remote_path ] ; do
     spin 0.05
     counter=$(( $counter+1 ))
     if ! (( $counter % 20)) ; then
-        new_size=$(adb_filesize "$remote_path")
-        unstable=$(($new_size != $last_size))
-        last_size=$new_size
-    fi
-    if [ $(date +%s) -gt $timeout ] ; then
-        printf '\bTimed out.\n'
-        exit 3
+        remote_path=$(get_filename)
     fi
 done
 printf '\b'
 
-printf "MSKP file serialized: %s\n" $(echo $last_size | numfmt --to=iec)
+printf "MSKP file serialized to: $remote_path\n"
 
-adb pull "$remote_path" "$local_path"
-if ! [ -f "$local_path" ] ; then
-    printf "something went wrong with `adb pull`."
-    exit 4
-fi
+adb_pull_cmd="adb pull $remote_path $local_path"
+echo $adb_pull_cmd
+$adb_pull_cmd
+
 adb shell rm "$remote_path"
-printf 'SKP saved to %s\n\n' "$local_path"
\ No newline at end of file
+printf 'SKP saved to %s\n\n' "$local_path"
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 72e32ed..1ca7a16 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -1265,6 +1265,7 @@
     renderengine::LayerSettings shadowLayer;
     shadowLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     shadowLayer.geometry.boundaries = castingBounds;
+    shadowLayer.skipContentDraw = true;
     shadowLayer.alpha = 1.0f;
     ColorSourceVariant::fillColor(shadowLayer, 0, 0, 0, this);
     shadowLayer.shadow = shadow;
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index b3537ce..ef3ceda 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -100,6 +100,15 @@
     }
 }
 
+void SensorPrivacyManager::removeIndividualSensorPrivacyListener(int sensor,
+        const sp<hardware::ISensorPrivacyListener>& listener)
+{
+    sp<hardware::ISensorPrivacyManager> service = getService();
+    if (service != nullptr) {
+        service->removeIndividualSensorPrivacyListener(sensor, listener);
+    }
+}
+
 bool SensorPrivacyManager::isSensorPrivacyEnabled()
 {
     sp<hardware::ISensorPrivacyManager> service = getService();
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index c8ceeb8..f91f5b9 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -28,6 +28,8 @@
 
     void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
 
+    void removeIndividualSensorPrivacyListener(int sensor, in ISensorPrivacyListener listener);
+
     boolean isSensorPrivacyEnabled();
 
     boolean isIndividualSensorPrivacyEnabled(int userId, int sensor);
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index fb4cbe9..af699d0 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -42,6 +42,8 @@
     status_t addIndividualSensorPrivacyListener(int userId, int sensor,
             const sp<hardware::ISensorPrivacyListener>& listener);
     void removeSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
+    void removeIndividualSensorPrivacyListener(int sensor,
+            const sp<hardware::ISensorPrivacyListener>& listener);
     bool isSensorPrivacyEnabled();
     bool isIndividualSensorPrivacyEnabled(int userId, int sensor);
     status_t isIndividualSensorPrivacyEnabled(int userId, int sensor, bool &result);
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 8073a93..dc0e60c 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -38,6 +38,14 @@
           "include-filter": "android.security.cts.MotionEventTest"
         }
       ]
+    },
+    {
+      "name": "CtsSecurityBulletinHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
+        }
+      ]
     }
   ]
 }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index c3b7c53..cf433c0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -89,6 +89,14 @@
 
 namespace android::inputdispatcher {
 
+// When per-window-input-rotation is enabled, InputFlinger works in the un-rotated display
+// coordinates and SurfaceFlinger includes the display rotation in the input window transforms.
+static bool isPerWindowInputRotationEnabled() {
+    static const bool PER_WINDOW_INPUT_ROTATION =
+            base::GetBoolProperty("persist.debug.per_window_input_rotation", false);
+    return PER_WINDOW_INPUT_ROTATION;
+}
+
 // Default input dispatching timeout if there is no focused application or paused window
 // from which to determine an appropriate dispatching timeout.
 const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::milliseconds(
@@ -4485,6 +4493,13 @@
     // Copy old handles for release if they are no longer present.
     const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
 
+    // Save the old windows' orientation by ID before it gets updated.
+    std::unordered_map<int32_t, uint32_t> oldWindowOrientations;
+    for (const sp<InputWindowHandle>& handle : oldWindowHandles) {
+        oldWindowOrientations.emplace(handle->getId(),
+                                      handle->getInfo()->transform.getOrientation());
+    }
+
     updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
 
     const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
@@ -4533,6 +4548,25 @@
         }
     }
 
+    if (isPerWindowInputRotationEnabled()) {
+        // Determine if the orientation of any of the input windows have changed, and cancel all
+        // pointer events if necessary.
+        for (const sp<InputWindowHandle>& oldWindowHandle : oldWindowHandles) {
+            const sp<InputWindowHandle> newWindowHandle = getWindowHandleLocked(oldWindowHandle);
+            if (newWindowHandle != nullptr &&
+                newWindowHandle->getInfo()->transform.getOrientation() !=
+                        oldWindowOrientations[oldWindowHandle->getId()]) {
+                std::shared_ptr<InputChannel> inputChannel =
+                        getInputChannelLocked(newWindowHandle->getToken());
+                if (inputChannel != nullptr) {
+                    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                                               "touched window's orientation changed");
+                    synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
+                }
+            }
+        }
+    }
+
     // Release information for windows that are no longer present.
     // This ensures that unused input channels are released promptly.
     // Otherwise, they might stick around until the window handle is destroyed
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 1a40d06..16251ee 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -104,7 +104,7 @@
     context.setLightBrightness(rawLightId, brightness);
 }
 
-bool PeripheralController::SingleLight::setLightColor(int32_t color) {
+bool PeripheralController::MonoLight::setLightColor(int32_t color) {
     int32_t brightness = getAlpha(color);
     setRawLightBrightness(rawId, brightness);
 
@@ -148,7 +148,7 @@
     return true;
 }
 
-std::optional<int32_t> PeripheralController::SingleLight::getLightColor() {
+std::optional<int32_t> PeripheralController::MonoLight::getLightColor() {
     std::optional<int32_t> brightness = getRawLightBrightness(rawId);
     if (!brightness.has_value()) {
         return std::nullopt;
@@ -234,7 +234,7 @@
     return std::nullopt;
 }
 
-void PeripheralController::SingleLight::dump(std::string& dump) {
+void PeripheralController::MonoLight::dump(std::string& dump) {
     dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
 }
 
@@ -423,7 +423,7 @@
                                                 playerIdLightIds);
         mLights.insert_or_assign(light->id, std::move(light));
         // Remove these raw lights from raw light info as they've been used to compose a
-        // Player ID light, so we do not expose these raw lights as single lights.
+        // Player ID light, so we do not expose these raw lights as mono lights.
         for (const auto& [playerId, rawId] : playerIdLightIds) {
             rawInfos.erase(rawId);
         }
@@ -460,13 +460,12 @@
             mLights.insert_or_assign(light->id, std::move(light));
             continue;
         }
-        // Construct a single LED light
+        // Construct a Mono LED light
         if (DEBUG_LIGHT_DETAILS) {
-            ALOGD("Single light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
+            ALOGD("Mono light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
         }
-        std::unique_ptr<Light> light =
-                std::make_unique<SingleLight>(getDeviceContext(), rawInfo.name, ++mNextId,
-                                              rawInfo.id);
+        std::unique_ptr<Light> light = std::make_unique<MonoLight>(getDeviceContext(), rawInfo.name,
+                                                                   ++mNextId, rawInfo.id);
 
         mLights.insert_or_assign(light->id, std::move(light));
     }
diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h
index ff3607f..b1bc8c7 100644
--- a/services/inputflinger/reader/controller/PeripheralController.h
+++ b/services/inputflinger/reader/controller/PeripheralController.h
@@ -78,10 +78,10 @@
         void setRawLightBrightness(int32_t rawLightId, int32_t brightness);
     };
 
-    struct SingleLight : public Light {
-        explicit SingleLight(InputDeviceContext& context, const std::string& name, int32_t id,
-                             int32_t rawId)
-              : Light(context, name, id, InputDeviceLightType::SINGLE), rawId(rawId) {}
+    struct MonoLight : public Light {
+        explicit MonoLight(InputDeviceContext& context, const std::string& name, int32_t id,
+                           int32_t rawId)
+              : Light(context, name, id, InputDeviceLightType::MONO), rawId(rawId) {}
         int32_t rawId;
 
         bool setLightColor(int32_t color) override;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index d6bd823..437902a 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -188,6 +188,8 @@
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         mOrientation = DISPLAY_ORIENTATION_0;
+        mDisplayWidth = 0;
+        mDisplayHeight = 0;
         const bool isOrientedDevice =
                 (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
 
@@ -202,6 +204,8 @@
                         config->getDisplayViewportByType(ViewportType::INTERNAL);
                 if (internalViewport) {
                     mOrientation = getInverseRotation(internalViewport->orientation);
+                    mDisplayWidth = internalViewport->deviceWidth;
+                    mDisplayHeight = internalViewport->deviceHeight;
                 }
             }
         } else {
@@ -360,13 +364,19 @@
         }
 
         mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+        if (isPerWindowInputRotationEnabled()) {
+            // Rotate the cursor position that is in PointerController's rotated coordinate space
+            // to InputReader's un-rotated coordinate space.
+            rotatePoint(mOrientation, xCursorPosition /*byRef*/, yCursorPosition /*byRef*/,
+                        mDisplayWidth, mDisplayHeight);
+        }
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
         displayId = mPointerController->getDisplayId();
-    } else if (mSource == AINPUT_SOURCE_MOUSE_RELATIVE) {
-        // Pointer capture mode
+    } else {
+        // Pointer capture and navigation modes
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 9a8ca01..88e947f 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -105,6 +105,8 @@
     VelocityControl mWheelYVelocityControl;
 
     int32_t mOrientation;
+    int32_t mDisplayWidth;
+    int32_t mDisplayHeight;
 
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 1843b03..da0fea4 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -68,6 +68,29 @@
             *deltaX = -*deltaY;
             *deltaY = temp;
             break;
+
+        default:
+            break;
+    }
+}
+
+// Rotates the given point (x, y) by the supplied orientation. The width and height are the
+// dimensions of the surface prior to this rotation being applied.
+static void rotatePoint(int32_t orientation, float& x, float& y, int32_t width, int32_t height) {
+    rotateDelta(orientation, &x, &y);
+    switch (orientation) {
+        case DISPLAY_ORIENTATION_90:
+            y += width;
+            break;
+        case DISPLAY_ORIENTATION_180:
+            x += width;
+            y += height;
+            break;
+        case DISPLAY_ORIENTATION_270:
+            x += height;
+            break;
+        default:
+            break;
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index fb65484..6050238 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -28,30 +28,6 @@
 
 namespace android {
 
-namespace {
-
-// Rotates the given point (x, y) by the supplied orientation. The width and height are the
-// dimensions of the surface prior to this rotation being applied.
-void rotatePoint(int32_t orientation, float& x, float& y, int32_t width, int32_t height) {
-    rotateDelta(orientation, &x, &y);
-    switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
-            y += width;
-            break;
-        case DISPLAY_ORIENTATION_180:
-            x += width;
-            y += height;
-            break;
-        case DISPLAY_ORIENTATION_270:
-            x += height;
-            break;
-        default:
-            break;
-    }
-}
-
-} // namespace
-
 // --- Constants ---
 
 // Maximum amount of latency to add to touch events while waiting for data from an
@@ -682,7 +658,9 @@
     int32_t rawHeight = mRawPointerAxes.getRawHeight();
 
     bool viewportChanged = mViewport != *newViewport;
+    bool skipViewportUpdate = false;
     if (viewportChanged) {
+        bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
         mViewport = *newViewport;
 
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
@@ -746,6 +724,8 @@
             mPhysicalLeft = naturalPhysicalLeft;
             mPhysicalTop = naturalPhysicalTop;
 
+            const int32_t oldSurfaceWidth = mRawSurfaceWidth;
+            const int32_t oldSurfaceHeight = mRawSurfaceHeight;
             mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
             mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
             mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
@@ -763,6 +743,11 @@
                 mSurfaceOrientation = mParameters.orientationAware
                         ? DISPLAY_ORIENTATION_0
                         : getInverseRotation(mViewport.orientation);
+                // For orientation-aware devices that work in the un-rotated coordinate space, the
+                // viewport update should be skipped if it is only a change in the orientation.
+                skipViewportUpdate = mParameters.orientationAware &&
+                        mRawSurfaceWidth == oldSurfaceWidth &&
+                        mRawSurfaceHeight == oldSurfaceHeight && viewportOrientationChanged;
             } else {
                 mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
                                                                    : DISPLAY_ORIENTATION_0;
@@ -802,7 +787,7 @@
         mPointerController.reset();
     }
 
-    if (viewportChanged || deviceModeChanged) {
+    if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
         ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
               "display id %d",
               getDeviceId(), getDeviceName().c_str(), mRawSurfaceWidth, mRawSurfaceHeight,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5eaca71..7a11ca7 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2768,18 +2768,22 @@
         ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
     }
 
-    static void assertPointerCoords(const PointerCoords& coords,
-            float x, float y, float pressure, float size,
-            float touchMajor, float touchMinor, float toolMajor, float toolMinor,
-            float orientation, float distance) {
-        ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), 1);
-        ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+    static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure,
+                                    float size, float touchMajor, float touchMinor, float toolMajor,
+                                    float toolMinor, float orientation, float distance,
+                                    float scaledAxisEpsilon = 1.f) {
+        ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon);
+        ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon);
         ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON);
         ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON);
-        ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), 1);
-        ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), 1);
-        ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), 1);
-        ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), 1);
+        ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+                    scaledAxisEpsilon);
+        ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+                    scaledAxisEpsilon);
+        ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+                    scaledAxisEpsilon);
+        ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+                    scaledAxisEpsilon);
         ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON);
         ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
     }
@@ -3822,6 +3826,12 @@
         setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                 orientation, uniqueId, NO_PORT, viewportType);
     }
+
+    static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y,
+                                          float pressure) {
+        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f,
+                                                    0.0f, 0.0f, 0.0f, EPSILON));
+    }
 };
 
 const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
@@ -3836,10 +3846,10 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
-            float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD,
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(args.pointerCoords[0],
+                                      float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+                                      float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
 }
 
 TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
@@ -3929,8 +3939,7 @@
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
@@ -3948,8 +3957,7 @@
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
@@ -3970,8 +3978,7 @@
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
@@ -3989,8 +3996,7 @@
     ASSERT_EQ(uint32_t(1), args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
     ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
@@ -4007,16 +4013,17 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
+                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f,
+                                                      0.0f));
 
     // Motion in Y but not X.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f,
+                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
 }
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
@@ -4030,26 +4037,22 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
 
     // Button release.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
 }
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
@@ -4065,15 +4068,15 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
+                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
+                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
 
     // Move X, Y a bit while pressed.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2);
@@ -4081,22 +4084,20 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
+                                                      2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
 
     // Release Button.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
 }
 
 TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) {
@@ -4178,15 +4179,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4194,22 +4195,22 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
@@ -4221,16 +4222,16 @@
             motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
             mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
             mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
@@ -4238,8 +4239,8 @@
             motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
             mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4247,15 +4248,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4263,8 +4264,8 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 
@@ -4272,15 +4273,15 @@
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     // press BTN_BACK, release BTN_BACK
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
@@ -4293,15 +4294,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4309,16 +4310,16 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
 
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -4334,15 +4335,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4350,15 +4351,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
@@ -4375,15 +4376,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4391,15 +4392,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
@@ -4416,15 +4417,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
@@ -4432,15 +4433,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
-            100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(
+            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
@@ -8743,20 +8744,20 @@
     }
 };
 
-TEST_F(LightControllerTest, SingleLight) {
-    RawLightInfo infoSingle = {.id = 1,
-                               .name = "Mono",
-                               .maxBrightness = 255,
-                               .flags = InputLightClass::BRIGHTNESS,
-                               .path = ""};
-    mFakeEventHub->addRawLightInfo(infoSingle.id, std::move(infoSingle));
+TEST_F(LightControllerTest, MonoLight) {
+    RawLightInfo infoMono = {.id = 1,
+                             .name = "Mono",
+                             .maxBrightness = 255,
+                             .flags = InputLightClass::BRIGHTNESS,
+                             .path = ""};
+    mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
 
     PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
     InputDeviceInfo info;
     controller.populateDeviceInfo(&info);
     const auto& ids = info.getLightIds();
     ASSERT_EQ(1UL, ids.size());
-    ASSERT_EQ(InputDeviceLightType::SINGLE, info.getLightInfo(ids[0])->type);
+    ASSERT_EQ(InputDeviceLightType::MONO, info.getLightInfo(ids[0])->type);
 
     ASSERT_TRUE(controller.setLightColor(ids[0], LIGHT_BRIGHTNESS));
     ASSERT_EQ(controller.getLightColor(ids[0]).value_or(-1), LIGHT_BRIGHTNESS);
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 73a6db5..560834f 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -17,6 +17,7 @@
 #include "SensorInterface.h"
 #include "SensorDevice.h"
 #include "SensorFusion.h"
+#include "SensorService.h"
 
 #include <stdint.h>
 #include <sys/types.h>
@@ -85,4 +86,35 @@
 }
 
 // ---------------------------------------------------------------------------
+
+ProximitySensor::ProximitySensor(const sensor_t& sensor, SensorService& service)
+        : HardwareSensor(sensor), mSensorService(service) {
+}
+
+status_t ProximitySensor::activate(void* ident, bool enabled) {
+    bool wasActive = mActive;
+    status_t status = HardwareSensor::activate(ident, enabled);
+    if (status != NO_ERROR) {
+        return status;
+    }
+    mActive = enabled;
+    if (wasActive != enabled) {
+        mSensorService.onProximityActiveLocked(enabled);
+    }
+    return NO_ERROR;
+}
+
+void ProximitySensor::willDisableAllSensors() {
+    if (mSensorDevice.isSensorActive(mSensor.getHandle())) {
+        mSensorService.onProximityActiveLocked(false);
+    }
+}
+
+void ProximitySensor::didEnableAllSensors() {
+    if (mSensorDevice.isSensorActive(mSensor.getHandle())) {
+        mSensorService.onProximityActiveLocked(true);
+    }
+}
+
+// ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h
index b5375cb..ea181c9 100644
--- a/services/sensorservice/SensorInterface.h
+++ b/services/sensorservice/SensorInterface.h
@@ -26,6 +26,7 @@
 // ---------------------------------------------------------------------------
 class SensorDevice;
 class SensorFusion;
+class SensorService;
 
 class SensorInterface : public VirtualLightRefBase {
 public:
@@ -43,6 +44,9 @@
     virtual const Sensor& getSensor() const = 0;
     virtual bool isVirtual() const = 0;
     virtual void autoDisable(void* /*ident*/, int /*handle*/) = 0;
+
+    virtual void willDisableAllSensors() = 0;
+    virtual void didEnableAllSensors() = 0;
 };
 
 class BaseSensor : public SensorInterface {
@@ -65,6 +69,9 @@
 
     virtual const Sensor& getSensor() const override { return mSensor; }
     virtual void autoDisable(void* /*ident*/, int /*handle*/) override { }
+
+    virtual void willDisableAllSensors() override { }
+    virtual void didEnableAllSensors() override { }
 protected:
     SensorDevice& mSensorDevice;
     Sensor mSensor;
@@ -100,6 +107,20 @@
     SensorFusion& mSensorFusion;
 };
 
+// ---------------------------------------------------------------------------
+
+class ProximitySensor : public HardwareSensor {
+public:
+    explicit ProximitySensor(const sensor_t& sensor, SensorService& service);
+
+    status_t activate(void* ident, bool enabled) override;
+
+    void willDisableAllSensors() override;
+    void didEnableAllSensors() override;
+private:
+    SensorService& mSensorService;
+    bool mActive;
+};
 
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h
index 617ceef..049ae7c 100644
--- a/services/sensorservice/SensorList.h
+++ b/services/sensorservice/SensorList.h
@@ -36,6 +36,15 @@
 
 class SensorList : public Dumpable {
 public:
+    struct Entry {
+        sp<SensorInterface> si;
+        const bool isForDebug;
+        const bool isVirtual;
+        Entry(SensorInterface* si_, bool debug_, bool virtual_) :
+            si(si_), isForDebug(debug_), isVirtual(virtual_) {
+        }
+    };
+
     // After SensorInterface * is added into SensorList, it can be assumed that SensorList own the
     // object it pointed to and the object should not be released elsewhere.
     bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false);
@@ -69,25 +78,6 @@
     template <typename TF>
     void forEachSensor(const TF& f) const;
 
-    const Sensor& getNonSensor() const { return mNonSensor;}
-
-    // Dumpable interface
-    virtual std::string dump() const override;
-    virtual void dump(util::ProtoOutputStream* proto) const override;
-
-    virtual ~SensorList();
-private:
-    struct Entry {
-        sp<SensorInterface> si;
-        const bool isForDebug;
-        const bool isVirtual;
-        Entry(SensorInterface* si_, bool debug_, bool virtual_) :
-            si(si_), isForDebug(debug_), isVirtual(virtual_) {
-        }
-    };
-
-    const static Sensor mNonSensor; //.getName() == "unknown",
-
     // Iterate through Entry in sensor list and perform operation f on each Entry.
     //
     // TF is a function with the signature:
@@ -99,6 +89,16 @@
     template <typename TF>
     void forEachEntry(const TF& f) const;
 
+    const Sensor& getNonSensor() const { return mNonSensor;}
+
+    // Dumpable interface
+    virtual std::string dump() const override;
+    virtual void dump(util::ProtoOutputStream* proto) const override;
+
+    virtual ~SensorList();
+private:
+    const static Sensor mNonSensor; //.getName() == "unknown",
+
     template <typename T, typename TF>
     T getOne(int handle, const TF& accessor, T def = T()) const;
 
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 9955cdb..9df020d 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -51,7 +51,6 @@
 #include "SensorRecord.h"
 #include "SensorRegistrationInfo.h"
 
-#include <ctime>
 #include <inttypes.h>
 #include <math.h>
 #include <sched.h>
@@ -61,8 +60,13 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <ctime>
+#include <future>
+
 #include <private/android_filesystem_config.h>
 
+using namespace std::chrono_literals;
+
 namespace android {
 // ---------------------------------------------------------------------------
 
@@ -83,6 +87,8 @@
 String16 SensorService::sSensorInterfaceDescriptorPrefix =
         String16("android.frameworks.sensorservice@");
 AppOpsManager SensorService::sAppOpsManager;
+std::atomic_uint64_t SensorService::curProxCallbackSeq(0);
+std::atomic_uint64_t SensorService::completedCallbackSeq(0);
 
 #define SENSOR_SERVICE_DIR "/data/system/sensor_service"
 #define SENSOR_SERVICE_HMAC_KEY_FILE  SENSOR_SERVICE_DIR "/hmac_key"
@@ -97,7 +103,7 @@
 
 SensorService::SensorService()
     : mInitCheck(NO_INIT), mSocketBufferSize(SOCKET_BUFFER_SIZE_NON_BATCHED),
-      mWakeLockAcquired(false) {
+      mWakeLockAcquired(false), mProximityActiveCount(0) {
     mUidPolicy = new UidPolicy(this);
     mSensorPrivacyPolicy = new SensorPrivacyPolicy(this);
 }
@@ -168,7 +174,7 @@
                     (1<<SENSOR_TYPE_GAME_ROTATION_VECTOR);
 
             for (ssize_t i=0 ; i<count ; i++) {
-                bool useThisSensor=true;
+                bool useThisSensor = true;
 
                 switch (list[i].type) {
                     case SENSOR_TYPE_ACCELEROMETER:
@@ -197,7 +203,11 @@
                         break;
                 }
                 if (useThisSensor) {
-                    registerSensor( new HardwareSensor(list[i]) );
+                    if (list[i].type == SENSOR_TYPE_PROXIMITY) {
+                        registerSensor(new ProximitySensor(list[i], *this));
+                    } else {
+                        registerSensor( new HardwareSensor(list[i]) );
+                    }
                 }
             }
 
@@ -670,6 +680,10 @@
         bool hasAccess = hasSensorAccessLocked(conn->getUid(), conn->getOpPackageName());
         conn->onSensorAccessChanged(hasAccess);
     }
+    mSensors.forEachEntry([](const SensorServiceUtil::SensorList::Entry& e) {
+        e.si->willDisableAllSensors();
+        return true;
+    });
     dev.disableAllSensors();
     // Clear all pending flush connections for all active sensors. If one of the active
     // connections has called flush() and the underlying sensor has been disabled before a
@@ -695,6 +709,10 @@
     }
     SensorDevice& dev(SensorDevice::getInstance());
     dev.enableAllSensors();
+    mSensors.forEachEntry([](const SensorServiceUtil::SensorList::Entry& e) {
+        e.si->didEnableAllSensors();
+        return true;
+    });
     for (const sp<SensorDirectConnection>& conn : connLock->getDirectConnections()) {
         bool hasAccess = hasSensorAccessLocked(conn->getUid(), conn->getOpPackageName());
         conn->onSensorAccessChanged(hasAccess);
@@ -1520,6 +1538,10 @@
     if (err == NO_ERROR) {
         mCurrentOperatingMode = NORMAL;
         dev.enableAllSensors();
+        mSensors.forEachEntry([](const SensorServiceUtil::SensorList::Entry& e) {
+            e.si->didEnableAllSensors();
+            return true;
+        });
     }
     return err;
 }
@@ -1584,6 +1606,80 @@
     mConnectionHolder.removeDirectConnection(c);
 }
 
+void SensorService::onProximityActiveLocked(bool isActive) {
+    int prevCount = mProximityActiveCount;
+    bool activeStateChanged = false;
+    if (isActive) {
+        mProximityActiveCount++;
+        activeStateChanged = prevCount == 0;
+    } else {
+        mProximityActiveCount--;
+        if (mProximityActiveCount < 0) {
+            ALOGE("Proximity active count is negative (%d)!", mProximityActiveCount);
+        }
+        activeStateChanged = prevCount > 0 && mProximityActiveCount <= 0;
+    }
+
+    if (activeStateChanged) {
+        notifyProximityStateLocked(mProximityActiveListeners);
+    }
+}
+
+void SensorService::notifyProximityStateLocked(
+        const std::vector<sp<ProximityActiveListener>>& listnrs) {
+    std::async(
+        std::launch::async,
+        [](uint64_t mySeq, bool isActive, std::vector<sp<ProximityActiveListener>> listeners) {
+            while (completedCallbackSeq.load() != mySeq - 1)
+                std::this_thread::sleep_for(1ms);
+            for (auto& listener : listeners)
+                listener->onProximityActive(isActive);
+            completedCallbackSeq++;
+        },
+        ++curProxCallbackSeq, mProximityActiveCount > 0,
+        listnrs /* (this is meant to be a copy) */
+    );
+}
+
+status_t SensorService::addProximityActiveListener(const sp<ProximityActiveListener>& callback) {
+    if (callback == nullptr) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock _l(mLock);
+
+    // Check if the callback was already added.
+    for (const auto& cb : mProximityActiveListeners) {
+        if (cb == callback) {
+            return ALREADY_EXISTS;
+        }
+    }
+
+    mProximityActiveListeners.push_back(callback);
+    std::vector<sp<ProximityActiveListener>> listener(1, callback);
+    notifyProximityStateLocked(listener);
+    return OK;
+}
+
+status_t SensorService::removeProximityActiveListener(
+        const sp<ProximityActiveListener>& callback) {
+    if (callback == nullptr) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock _l(mLock);
+
+    for (auto iter = mProximityActiveListeners.begin();
+         iter != mProximityActiveListeners.end();
+         ++iter) {
+        if (*iter == callback) {
+            mProximityActiveListeners.erase(iter);
+            return OK;
+        }
+    }
+    return NAME_NOT_FOUND;
+}
+
 sp<SensorInterface> SensorService::getSensorInterfaceFromHandle(int handle) const {
     return mSensors.getInterface(handle);
 }
@@ -2138,7 +2234,12 @@
 void SensorService::SensorPrivacyPolicy::unregisterSelf() {
     AutoCallerClear acc;
     SensorPrivacyManager spm;
-    spm.removeSensorPrivacyListener(this);
+    if (mIsIndividualMic) {
+        spm.removeIndividualSensorPrivacyListener(
+                SensorPrivacyManager::INDIVIDUAL_SENSOR_MICROPHONE, this);
+    } else {
+        spm.removeSensorPrivacyListener(this);
+    }
 }
 
 bool SensorService::SensorPrivacyPolicy::isSensorPrivacyEnabled() {
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index a563a60..def6611 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -89,9 +89,23 @@
       UID_STATE_IDLE,
     };
 
+    class ProximityActiveListener : public virtual RefBase {
+    public:
+        // Note that the callback is invoked from an async thread and can interact with the
+        // SensorService directly.
+        virtual void onProximityActive(bool isActive) = 0;
+    };
+
+    static char const* getServiceName() ANDROID_API { return "sensorservice"; }
+    SensorService() ANDROID_API;
+
     void cleanupConnection(SensorEventConnection* connection);
     void cleanupConnection(SensorDirectConnection* c);
 
+    // Call with mLock held.
+    void onProximityActiveLocked(bool isActive);
+    void notifyProximityStateLocked(const std::vector<sp<ProximityActiveListener>>& listeners);
+
     status_t enable(const sp<SensorEventConnection>& connection, int handle,
                     nsecs_t samplingPeriodNs,  nsecs_t maxBatchReportLatencyNs, int reservedFlags,
                     const String16& opPackageName);
@@ -104,6 +118,9 @@
     status_t flushSensor(const sp<SensorEventConnection>& connection,
                          const String16& opPackageName);
 
+    status_t addProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
+    status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
+
     // Returns true if a sensor should be throttled according to our rate-throttling rules.
     static bool isSensorInCappedSet(int sensorType);
 
@@ -305,8 +322,6 @@
     };
 
     static const char* WAKE_LOCK_NAME;
-    static char const* getServiceName() ANDROID_API { return "sensorservice"; }
-    SensorService() ANDROID_API;
     virtual ~SensorService();
 
     virtual void onFirstRef();
@@ -326,6 +341,7 @@
     virtual int setOperationParameter(
             int32_t handle, int32_t type, const Vector<float> &floats, const Vector<int32_t> &ints);
     virtual status_t dump(int fd, const Vector<String16>& args);
+
     status_t dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock) const;
     String8 getSensorName(int handle) const;
     String8 getSensorStringType(int handle) const;
@@ -433,6 +449,9 @@
     static uint8_t sHmacGlobalKey[128];
     static bool sHmacGlobalKeyIsValid;
 
+    static std::atomic_uint64_t curProxCallbackSeq;
+    static std::atomic_uint64_t completedCallbackSeq;
+
     SensorServiceUtil::SensorList mSensors;
     status_t mInitCheck;
 
@@ -476,6 +495,10 @@
     std::map<userid_t, sp<SensorPrivacyPolicy>> mMicSensorPrivacyPolicies;
     // Checks if the mic sensor privacy is enabled for the uid
     bool isMicSensorPrivacyEnabledForUid(uid_t uid);
+
+    // Counts how many proximity sensors are currently active.
+    int mProximityActiveCount;
+    std::vector<sp<ProximityActiveListener>> mProximityActiveListeners;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index f20bfe1..e669e45 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -188,6 +188,7 @@
         "SurfaceInterceptor.cpp",
         "SurfaceTracing.cpp",
         "TransactionCallbackInvoker.cpp",
+        "TunnelModeEnabledReporter.cpp",
     ],
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index bf36355..088a400 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -136,6 +136,14 @@
     } else {
         mPlanner.reset();
     }
+
+    for (auto* outputLayer : getOutputLayersOrderedByZ()) {
+        if (!outputLayer) {
+            continue;
+        }
+
+        outputLayer->editState().overrideInfo = {};
+    }
 }
 
 void Output::setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 63085cc..b61daeb 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -17,6 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "Planner"
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -25,6 +26,8 @@
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/RenderEngine.h>
 
+#include <utils/Trace.h>
+
 namespace android::compositionengine::impl::planner {
 
 const bool CachedSet::sDebugHighlighLayers =
@@ -154,6 +157,7 @@
 
 void CachedSet::render(renderengine::RenderEngine& renderEngine,
                        const OutputCompositionState& outputState) {
+    ATRACE_CALL();
     const Rect& viewport = outputState.layerStackSpace.content;
     const ui::Dataspace& outputDataspace = outputState.dataspace;
     const ui::Transform::RotationFlags orientation =
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index fdd73af..2def99d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -17,10 +17,13 @@
 #undef LOG_TAG
 #define LOG_TAG "Planner"
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
+#include <utils/Trace.h>
+
 using time_point = std::chrono::steady_clock::time_point;
 using namespace std::chrono_literals;
 
@@ -58,6 +61,7 @@
 
 NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
                                        NonBufferHash hash, time_point now) {
+    ATRACE_CALL();
     const size_t unflattenedDisplayCost = calculateDisplayCost(layers);
     mUnflattenedDisplayCost += unflattenedDisplayCost;
 
@@ -91,6 +95,7 @@
 
 void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine,
                                  const OutputCompositionState& outputState) {
+    ATRACE_CALL();
     if (!mNewCachedSet || mNewCachedSet->hasRenderedBuffer()) {
         return;
     }
@@ -213,6 +218,7 @@
 // was already populated with these layers, i.e. on the second and following
 // calls with the same geometry.
 bool Flattener::mergeWithCachedSets(const std::vector<const LayerState*>& layers, time_point now) {
+    ATRACE_CALL();
     std::vector<CachedSet> merged;
 
     if (mLayers.empty()) {
@@ -328,6 +334,7 @@
 }
 
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
+    ATRACE_CALL();
     std::vector<Run> runs;
     bool isPartOfRun = false;
     Run::Builder builder;
@@ -388,6 +395,7 @@
 }
 
 void Flattener::buildCachedSets(time_point now) {
+    ATRACE_CALL();
     if (mLayers.empty()) {
         ALOGV("[%s] No layers found, returning", __func__);
         return;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index c26eb1e..297c0b2 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -18,19 +18,23 @@
 
 #undef LOG_TAG
 #define LOG_TAG "Planner"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
 
+#include <utils/Trace.h>
+
 namespace android::compositionengine::impl::planner {
 
 Planner::Planner()
-      : mFlattener(base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), false)) {
-    // Implicitly, layer caching must also be enabled.
-    // E.g., setprop debug.sf.enable_layer_caching 1, or
-    // adb shell service call SurfaceFlinger 1040 i32 1 [i64 <display ID>]
+      // Implicitly, layer caching must also be enabled for the hole punch or
+      // predictor to have any effect.
+      // E.g., setprop debug.sf.enable_layer_caching 1, or
+      // adb shell service call SurfaceFlinger 1040 i32 1 [i64 <display ID>]
+      : mFlattener(base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), true)) {
     mPredictorEnabled =
             base::GetBoolProperty(std::string("debug.sf.enable_planner_prediction"), false);
 }
@@ -41,6 +45,7 @@
 
 void Planner::plan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    ATRACE_CALL();
     std::unordered_set<LayerId> removedLayers;
     removedLayers.reserve(mPreviousLayers.size());
 
@@ -119,6 +124,7 @@
 
 void Planner::reportFinalPlan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    ATRACE_CALL();
     if (!mPredictorEnabled) {
         return;
     }
@@ -156,6 +162,7 @@
 
 void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine,
                                const OutputCompositionState& outputState) {
+    ATRACE_CALL();
     mFlattener.renderCachedSets(renderEngine, outputState);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 11736d1..6677f40 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -259,6 +259,27 @@
     EXPECT_FALSE(mOutput->plannerEnabled());
 }
 
+TEST_F(OutputTest, setLayerCachingEnabled_disablesCachingAndResetsOverrideInfo) {
+    renderengine::mock::RenderEngine renderEngine;
+    const auto kSize = ui::Size(1, 1);
+    EXPECT_CALL(*mRenderSurface, getSize()).WillRepeatedly(ReturnRef(kSize));
+    mOutput->setLayerCachingEnabled(true);
+
+    // Inject some layers
+    InjectedLayer layer;
+    layer.outputLayerState.overrideInfo.buffer = std::make_shared<
+            renderengine::ExternalTexture>(new GraphicBuffer(), renderEngine,
+                                           renderengine::ExternalTexture::Usage::READABLE |
+                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+    injectOutputLayer(layer);
+    // inject a null layer to check for null exceptions
+    injectNullOutputLayer();
+
+    EXPECT_NE(nullptr, layer.outputLayerState.overrideInfo.buffer);
+    mOutput->setLayerCachingEnabled(false);
+    EXPECT_EQ(nullptr, layer.outputLayerState.overrideInfo.buffer);
+}
+
 /*
  * Output::setProjection()
  */
diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp
index caef338..0cc5f33 100644
--- a/services/surfaceflinger/EffectLayer.cpp
+++ b/services/surfaceflinger/EffectLayer.cpp
@@ -57,19 +57,16 @@
         return {};
     }
 
-    std::optional<compositionengine::LayerFE::LayerSettings> shadowSettings =
-            prepareShadowClientComposition(*layerSettings, targetSettings.viewport,
-                                           targetSettings.dataspace);
-    if (shadowSettings) {
-        results.push_back(*shadowSettings);
-    }
+    // set the shadow for the layer if needed
+    prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
 
     // If fill bounds are occluded or the fill color is invalid skip the fill settings.
     if (targetSettings.realContentIsVisible && fillsColor()) {
         // Set color for color fill settings.
         layerSettings->source.solidColor = getColor().rgb;
         results.push_back(*layerSettings);
-    } else if (hasBlur()) {
+    } else if (hasBlur() || drawShadows()) {
+        layerSettings->skipContentDraw = true;
         results.push_back(*layerSettings);
     }
 
@@ -130,7 +127,7 @@
 bool EffectLayer::isOpaque(const Layer::State& s) const {
     // Consider the layer to be opaque if its opaque flag is set or its effective
     // alpha (considering the alpha of its parents as well) is 1.0;
-    return (s.flags & layer_state_t::eLayerOpaque) != 0 || getAlpha() == 1.0_hf;
+    return (s.flags & layer_state_t::eLayerOpaque) != 0 || (fillsColor() && getAlpha() == 1.0_hf);
 }
 
 ui::Dataspace EffectLayer::getDataSpace() const {
@@ -151,7 +148,7 @@
 }
 
 bool EffectLayer::hasBlur() const {
-    return getBackgroundBlurRadius() > 0;
+    return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 8b7dc4d..690fda6 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -573,6 +573,10 @@
     layerSettings.geometry.boundaries = bounds;
     layerSettings.geometry.positionTransform = getTransform().asMatrix4();
 
+    // skip drawing content if the targetSettings indicate the content will be occluded
+    layerSettings.skipContentDraw =
+            layerSettings.skipContentDraw || !targetSettings.realContentIsVisible;
+
     if (hasColorTransform()) {
         layerSettings.colorTransform = getColorTransform();
     }
@@ -595,42 +599,6 @@
     return layerSettings;
 }
 
-std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareShadowClientComposition(
-        const LayerFE::LayerSettings& casterLayerSettings, const Rect& layerStackRect,
-        ui::Dataspace outputDataspace) {
-    renderengine::ShadowSettings shadow = getShadowSettings(layerStackRect);
-    if (shadow.length <= 0.f) {
-        return {};
-    }
-
-    const float casterAlpha = casterLayerSettings.alpha;
-    const bool casterIsOpaque = ((casterLayerSettings.source.buffer.buffer != nullptr) &&
-                                 casterLayerSettings.source.buffer.isOpaque);
-
-    compositionengine::LayerFE::LayerSettings shadowLayer = casterLayerSettings;
-
-    shadowLayer.shadow = shadow;
-    shadowLayer.geometry.boundaries = mBounds; // ignore transparent region
-
-    // If the casting layer is translucent, we need to fill in the shadow underneath the layer.
-    // Otherwise the generated shadow will only be shown around the casting layer.
-    shadowLayer.shadow.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f);
-    shadowLayer.shadow.ambientColor *= casterAlpha;
-    shadowLayer.shadow.spotColor *= casterAlpha;
-    shadowLayer.sourceDataspace = outputDataspace;
-    shadowLayer.source.buffer.buffer = nullptr;
-    shadowLayer.source.buffer.fence = nullptr;
-    shadowLayer.frameNumber = 0;
-    shadowLayer.bufferId = 0;
-    shadowLayer.name = getName();
-
-    if (shadowLayer.shadow.ambientColor.a <= 0.f && shadowLayer.shadow.spotColor.a <= 0.f) {
-        return {};
-    }
-
-    return shadowLayer;
-}
-
 void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings,
                                           bool blackout) const {
     layerSettings.source.buffer.buffer = nullptr;
@@ -644,6 +612,9 @@
     layerSettings.name = getName();
 }
 
+// TODO(b/188891810): This method now only ever returns 0 or 1 layers so we should return
+// std::optional instead of a vector.  Additionally, we should consider removing
+// this method entirely in favor of calling prepareClientComposition directly.
 std::vector<compositionengine::LayerFE::LayerSettings> Layer::prepareClientCompositionList(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
@@ -659,21 +630,10 @@
         return {*layerSettings};
     }
 
-    std::optional<compositionengine::LayerFE::LayerSettings> shadowSettings =
-            prepareShadowClientComposition(*layerSettings, targetSettings.viewport,
-                                           targetSettings.dataspace);
-    // There are no shadows to render.
-    if (!shadowSettings) {
-        return {*layerSettings};
-    }
+    // set the shadow for the layer if needed
+    prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
 
-    // If the layer casts a shadow but the content casting the shadow is occluded, skip
-    // composing the non-shadow content and only draw the shadows.
-    if (targetSettings.realContentIsVisible) {
-        return {*shadowSettings, *layerSettings};
-    }
-
-    return {*shadowSettings};
+    return {*layerSettings};
 }
 
 Hwc2::IComposerClient::Composition Layer::getCompositionType(const DisplayDevice& display) const {
@@ -2061,16 +2021,37 @@
             : RoundedCornerState();
 }
 
-renderengine::ShadowSettings Layer::getShadowSettings(const Rect& layerStackRect) const {
+void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
+                                           const Rect& layerStackRect) {
     renderengine::ShadowSettings state = mFlinger->mDrawingState.globalShadowSettings;
 
+    // Note: this preserves existing behavior of shadowing the entire layer and not cropping it if
+    // transparent regions are present. This may not be necessary since shadows are only cast by
+    // SurfaceFlinger's EffectLayers, which do not typically use transparent regions.
+    state.boundaries = mBounds;
+
     // Shift the spot light x-position to the middle of the display and then
     // offset it by casting layer's screen pos.
     state.lightPos.x = (layerStackRect.width() / 2.f) - mScreenBounds.left;
     state.lightPos.y -= mScreenBounds.top;
 
     state.length = mEffectiveShadowRadius;
-    return state;
+
+    if (state.length > 0.f) {
+        const float casterAlpha = caster.alpha;
+        const bool casterIsOpaque =
+                ((caster.source.buffer.buffer != nullptr) && caster.source.buffer.isOpaque);
+
+        // If the casting layer is translucent, we need to fill in the shadow underneath the layer.
+        // Otherwise the generated shadow will only be shown around the casting layer.
+        state.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f);
+        state.ambientColor *= casterAlpha;
+        state.spotColor *= casterAlpha;
+
+        if (state.ambientColor.a > 0.f && state.spotColor.a > 0.f) {
+            caster.shadow = state;
+        }
+    }
 }
 
 void Layer::commitChildList() {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 8139d8a..66d7018 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -764,8 +764,6 @@
     // is ready to acquire a buffer.
     ui::Transform::RotationFlags getFixedTransformHint() const;
 
-    renderengine::ShadowSettings getShadowSettings(const Rect& layerStackRect) const;
-
     /**
      * Traverse this layer and it's hierarchy of children directly. Unlike traverseInZOrder
      * which will not emit children who have relativeZOrder to another layer, this method
@@ -895,9 +893,6 @@
     virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom);
     virtual std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&);
-    virtual std::optional<compositionengine::LayerFE::LayerSettings> prepareShadowClientComposition(
-            const LayerFE::LayerSettings&, const Rect& layerStackRect,
-            ui::Dataspace outputDataspace);
     virtual void preparePerFrameCompositionState();
     virtual void commitTransaction(State& stateToCommit);
     virtual uint32_t doTransactionResize(uint32_t flags, Layer::State* stateToCommit);
@@ -923,6 +918,7 @@
     // Modifies the passed in layer settings to clear the contents. If the blackout flag is set,
     // the settings clears the content with a solid black fill.
     void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const;
+    void prepareShadowClientComposition(LayerFE::LayerSettings& caster, const Rect& layerStackRect);
 
     void prepareBasicGeometryCompositionState();
     void prepareGeometryCompositionState();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a5b7107..d23553f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -23,7 +23,6 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "SurfaceFlinger.h"
-#include "TraceUtils.h"
 
 #include <android-base/properties.h>
 #include <android/configuration.h>
@@ -58,6 +57,7 @@
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
+#include <gui/TraceUtils.h>
 #include <hidl/ServiceManagement.h>
 #include <layerproto/LayerProtoParser.h>
 #include <log/log.h>
@@ -130,6 +130,7 @@
 #include "SurfaceFlingerProperties.h"
 #include "SurfaceInterceptor.h"
 #include "TimeStats/TimeStats.h"
+#include "TunnelModeEnabledReporter.h"
 #include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -788,11 +789,6 @@
 
     property_get("persist.sys.sf.color_mode", value, "0");
     mForceColorMode = static_cast<ColorMode>(atoi(value));
-
-    property_get("persist.sys.sf.disable_blurs", value, "0");
-    bool disableBlurs = atoi(value);
-    mDisableBlurs = disableBlurs;
-    ALOGI_IF(disableBlurs, "Disabling blur effects, user preference.");
 }
 
 void SurfaceFlinger::startBootAnim() {
@@ -1468,6 +1464,26 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::addTunnelModeEnabledListener(
+        const sp<gui::ITunnelModeEnabledListener>& listener) {
+    if (!listener) {
+        return BAD_VALUE;
+    }
+
+    mTunnelModeEnabledReporter->addListener(listener);
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::removeTunnelModeEnabledListener(
+        const sp<gui::ITunnelModeEnabledListener>& listener) {
+    if (!listener) {
+        return BAD_VALUE;
+    }
+
+    mTunnelModeEnabledReporter->removeListener(listener);
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
                                                      bool* outSupport) const {
     if (!displayToken || !outSupport) {
@@ -2191,6 +2207,10 @@
         if (mFpsReporter) {
             mFpsReporter->dispatchLayerFps();
         }
+
+        if (mTunnelModeEnabledReporter) {
+            mTunnelModeEnabledReporter->updateTunnelModeStatus();
+        }
         hdrInfoListeners.reserve(mHdrLayerInfoListeners.size());
         for (auto& [key, value] : mHdrLayerInfoListeners) {
             if (value && value->hasListeners()) {
@@ -3067,6 +3087,7 @@
     mRegionSamplingThread =
             new RegionSamplingThread(*this, RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = new FpsReporter(*mFrameTimeline, *this);
+    mTunnelModeEnabledReporter = new TunnelModeEnabledReporter(*this);
     // Dispatch a mode change request for the primary display on scheduler
     // initialization, so that the EventThreads always contain a reference to a
     // prior configuration.
@@ -3927,7 +3948,7 @@
         if (layer->setCornerRadius(s.cornerRadius))
             flags |= eTraversalNeeded;
     }
-    if (what & layer_state_t::eBackgroundBlurRadiusChanged && !mDisableBlurs && mSupportsBlur) {
+    if (what & layer_state_t::eBackgroundBlurRadiusChanged && mSupportsBlur) {
         if (layer->setBackgroundBlurRadius(s.backgroundBlurRadius)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eBlurRegionsChanged) {
@@ -5072,6 +5093,8 @@
         case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES:
         case SET_DISPLAY_CONTENT_SAMPLING_ENABLED:
         case GET_DISPLAYED_CONTENT_SAMPLE:
+        case ADD_TUNNEL_MODE_ENABLED_LISTENER:
+        case REMOVE_TUNNEL_MODE_ENABLED_LISTENER:
         case NOTIFY_POWER_BOOST:
         case SET_GLOBAL_SHADOW_SETTINGS:
         case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
@@ -5595,26 +5618,35 @@
             // Second argument is an optional uint64 - if present, then limits enabling/disabling
             // caching to a particular physical display
             case 1040: {
-                n = data.readInt32();
-                std::optional<PhysicalDisplayId> inputId = std::nullopt;
-                if (uint64_t inputDisplayId; data.readUint64(&inputDisplayId) == NO_ERROR) {
-                    const auto token =
-                            getPhysicalDisplayToken(static_cast<PhysicalDisplayId>(inputDisplayId));
-                    if (!token) {
-                        ALOGE("No display with id: %" PRIu64, inputDisplayId);
-                        return NAME_NOT_FOUND;
-                    }
+                status_t error =
+                        schedule([&] {
+                            n = data.readInt32();
+                            std::optional<PhysicalDisplayId> inputId = std::nullopt;
+                            if (uint64_t inputDisplayId;
+                                data.readUint64(&inputDisplayId) == NO_ERROR) {
+                                const auto token = getPhysicalDisplayToken(
+                                        static_cast<PhysicalDisplayId>(inputDisplayId));
+                                if (!token) {
+                                    ALOGE("No display with id: %" PRIu64, inputDisplayId);
+                                    return NAME_NOT_FOUND;
+                                }
 
-                    inputId = std::make_optional<PhysicalDisplayId>(inputDisplayId);
-                }
-                {
-                    Mutex::Autolock lock(mStateLock);
-                    mLayerCachingEnabled = n != 0;
-                    for (const auto& [_, display] : mDisplays) {
-                        if (!inputId || *inputId == display->getPhysicalId()) {
-                            display->enableLayerCaching(mLayerCachingEnabled);
-                        }
-                    }
+                                inputId = std::make_optional<PhysicalDisplayId>(inputDisplayId);
+                            }
+                            {
+                                Mutex::Autolock lock(mStateLock);
+                                mLayerCachingEnabled = n != 0;
+                                for (const auto& [_, display] : mDisplays) {
+                                    if (!inputId || *inputId == display->getPhysicalId()) {
+                                        display->enableLayerCaching(mLayerCachingEnabled);
+                                    }
+                                }
+                            }
+                            return OK;
+                        }).get();
+
+                if (error != OK) {
+                    return error;
                 }
                 invalidateHwcGeometry();
                 repaintEverything();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index cb8d312..30d76af 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -90,6 +90,7 @@
 class Client;
 class EventThread;
 class FpsReporter;
+class TunnelModeEnabledReporter;
 class HdrLayerInfoReporter;
 class HWComposer;
 struct SetInputWindowsListener;
@@ -359,6 +360,7 @@
     friend class BufferStateLayer;
     friend class Client;
     friend class FpsReporter;
+    friend class TunnelModeEnabledReporter;
     friend class Layer;
     friend class MonitoredProducer;
     friend class RefreshRateOverlay;
@@ -673,6 +675,10 @@
     status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener) override;
     status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) override;
     status_t removeFpsListener(const sp<gui::IFpsListener>& listener) override;
+    status_t addTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) override;
+    status_t removeTunnelModeEnabledListener(
+            const sp<gui::ITunnelModeEnabledListener>& listener) override;
     status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                         ui::DisplayModeId displayModeId, bool allowGroupSwitching,
                                         float primaryRefreshRateMin, float primaryRefreshRateMax,
@@ -1258,8 +1264,6 @@
     bool mUseHwcVirtualDisplays = false;
     // If blurs should be enabled on this device.
     bool mSupportsBlur = false;
-    // Disable blurs, for debugging
-    std::atomic<bool> mDisableBlurs = false;
     // If blurs are considered expensive and should require high GPU frequency.
     bool mBlursAreExpensive = false;
     std::atomic<uint32_t> mFrameMissedCount = 0;
@@ -1368,6 +1372,7 @@
     bool mLumaSampling = true;
     sp<RegionSamplingThread> mRegionSamplingThread;
     sp<FpsReporter> mFpsReporter;
+    sp<TunnelModeEnabledReporter> mTunnelModeEnabledReporter;
     ui::DisplayPrimaries mInternalDisplayPrimaries;
 
     const float mInternalDisplayDensity;
diff --git a/services/surfaceflinger/TunnelModeEnabledReporter.cpp b/services/surfaceflinger/TunnelModeEnabledReporter.cpp
new file mode 100644
index 0000000..1b3ddf7
--- /dev/null
+++ b/services/surfaceflinger/TunnelModeEnabledReporter.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 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 "TunnelModeEnabledReporter"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <algorithm>
+
+#include "Layer.h"
+#include "SurfaceFlinger.h"
+#include "TunnelModeEnabledReporter.h"
+
+namespace android {
+
+TunnelModeEnabledReporter::TunnelModeEnabledReporter(SurfaceFlinger& flinger) : mFlinger(flinger) {}
+
+void TunnelModeEnabledReporter::updateTunnelModeStatus() {
+    bool tunnelModeEnabled = false;
+    mFlinger.mCurrentState.traverse([&](Layer* layer) {
+        auto& currentState = layer->getCurrentState();
+        if (currentState.sidebandStream != nullptr) {
+            tunnelModeEnabled = true;
+            return;
+        }
+    });
+    dispatchTunnelModeEnabled(tunnelModeEnabled);
+}
+
+void TunnelModeEnabledReporter::dispatchTunnelModeEnabled(bool tunnelModeEnabled) {
+    std::vector<sp<gui::ITunnelModeEnabledListener>> localListeners;
+    {
+        std::scoped_lock lock(mMutex);
+        if (mTunnelModeEnabled == tunnelModeEnabled) {
+            return;
+        }
+        mTunnelModeEnabled = tunnelModeEnabled;
+
+        std::transform(mListeners.begin(), mListeners.end(), std::back_inserter(localListeners),
+                       [](const std::pair<wp<IBinder>, sp<gui::ITunnelModeEnabledListener>>&
+                                  entry) { return entry.second; });
+    }
+
+    for (sp<gui::ITunnelModeEnabledListener>& listener : localListeners) {
+        listener->onTunnelModeEnabledChanged(tunnelModeEnabled);
+    }
+}
+
+void TunnelModeEnabledReporter::binderDied(const wp<IBinder>& who) {
+    std::scoped_lock lock(mMutex);
+    mListeners.erase(who);
+}
+
+void TunnelModeEnabledReporter::addListener(const sp<gui::ITunnelModeEnabledListener>& listener) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
+    asBinder->linkToDeath(this);
+    bool tunnelModeEnabled = false;
+    {
+        std::scoped_lock lock(mMutex);
+        mListeners.emplace(wp<IBinder>(asBinder), listener);
+        tunnelModeEnabled = mTunnelModeEnabled;
+    }
+    listener->onTunnelModeEnabledChanged(tunnelModeEnabled);
+}
+
+void TunnelModeEnabledReporter::removeListener(
+        const sp<gui::ITunnelModeEnabledListener>& listener) {
+    std::lock_guard lock(mMutex);
+    mListeners.erase(wp<IBinder>(IInterface::asBinder(listener)));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/TunnelModeEnabledReporter.h b/services/surfaceflinger/TunnelModeEnabledReporter.h
new file mode 100644
index 0000000..d55507a
--- /dev/null
+++ b/services/surfaceflinger/TunnelModeEnabledReporter.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <android/gui/ITunnelModeEnabledListener.h>
+#include <binder/IBinder.h>
+
+#include <unordered_map>
+
+namespace android {
+
+class Layer;
+class SurfaceFlinger;
+
+class TunnelModeEnabledReporter : public IBinder::DeathRecipient {
+public:
+    TunnelModeEnabledReporter(SurfaceFlinger& flinger);
+
+    // Checks if there is a tunnel mode enabled state change and if so, dispatches the updated
+    // tunnel mode enabled/disabled state to the registered listeners
+    // This method performs layer stack traversals, so mStateLock must be held when calling this
+    // method.
+    void updateTunnelModeStatus();
+
+    // Dispatches tunnelModeEnabled to all registered listeners
+    void dispatchTunnelModeEnabled(bool tunnelModeEnabled);
+
+    // Override for IBinder::DeathRecipient
+    void binderDied(const wp<IBinder>&) override;
+
+    // Registers a TunnelModeEnabled listener
+    void addListener(const sp<gui::ITunnelModeEnabledListener>& listener);
+
+    // Deregisters a TunnelModeEnabled listener
+    void removeListener(const sp<gui::ITunnelModeEnabledListener>& listener);
+
+private:
+    mutable std::mutex mMutex;
+    struct WpHash {
+        size_t operator()(const wp<IBinder>& p) const {
+            return std::hash<IBinder*>()(p.unsafe_get());
+        }
+    };
+
+    SurfaceFlinger& mFlinger;
+    std::unordered_map<wp<IBinder>, sp<gui::ITunnelModeEnabledListener>, WpHash> mListeners
+            GUARDED_BY(mMutex);
+    bool mTunnelModeEnabled GUARDED_BY(mMutex) = false;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index b33434e..1b25a36 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -86,6 +86,7 @@
         "TransactionApplicationTest.cpp",
         "TransactionFrameTracerTest.cpp",
         "TransactionSurfaceFrameTest.cpp",
+        "TunnelModeEnabledReporterTest.cpp",
         "StrongTypingTest.cpp",
         "VSyncDispatchTimerQueueTest.cpp",
         "VSyncDispatchRealtimeTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index d004b9d..a551248 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -276,6 +276,7 @@
     static void setLayerSidebandStream(const sp<Layer>& layer,
                                        const sp<NativeHandle>& sidebandStream) {
         layer->mDrawingState.sidebandStream = sidebandStream;
+        layer->mCurrentState.sidebandStream = sidebandStream;
         layer->mSidebandStream = sidebandStream;
         layer->editCompositionState()->sidebandStream = sidebandStream;
     }
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
new file mode 100644
index 0000000..d7d7ea7
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021 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 "TunnelModeEnabledReporterTest"
+
+#include <android/gui/BnTunnelModeEnabledListener.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "BufferStateLayer.h"
+#include "TestableSurfaceFlinger.h"
+#include "TunnelModeEnabledReporter.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/MockEventThread.h"
+
+namespace android {
+
+using testing::_;
+using testing::Mock;
+using testing::Return;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+constexpr int DEFAULT_SIDEBAND_STREAM = 51;
+
+struct TestableTunnelModeEnabledListener : public gui::BnTunnelModeEnabledListener {
+    TestableTunnelModeEnabledListener() {}
+
+    bool mTunnelModeEnabled = false;
+
+    binder::Status onTunnelModeEnabledChanged(bool tunnelModeEnabled) override {
+        mTunnelModeEnabled = tunnelModeEnabled;
+        return binder::Status::ok();
+    }
+};
+
+class TunnelModeEnabledReporterTest : public testing::Test {
+public:
+    TunnelModeEnabledReporterTest();
+    ~TunnelModeEnabledReporterTest() override;
+
+protected:
+    static constexpr uint32_t WIDTH = 100;
+    static constexpr uint32_t HEIGHT = 100;
+    static constexpr uint32_t LAYER_FLAGS = 0;
+
+    void setupScheduler();
+    void setupComposer(uint32_t virtualDisplayCount);
+    sp<BufferStateLayer> createBufferStateLayer(LayerMetadata metadata);
+
+    TestableSurfaceFlinger mFlinger;
+    Hwc2::mock::Composer* mComposer = nullptr;
+    sp<TestableTunnelModeEnabledListener> mTunnelModeEnabledListener =
+            new TestableTunnelModeEnabledListener();
+    sp<TunnelModeEnabledReporter> mTunnelModeEnabledReporter =
+            new TunnelModeEnabledReporter(*(mFlinger.flinger()));
+};
+
+TunnelModeEnabledReporterTest::TunnelModeEnabledReporterTest() {
+    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());
+    setupScheduler();
+    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false);
+}
+
+TunnelModeEnabledReporterTest::~TunnelModeEnabledReporterTest() {
+    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());
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false);
+    mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener);
+}
+
+sp<BufferStateLayer> TunnelModeEnabledReporterTest::createBufferStateLayer(
+        LayerMetadata metadata = {}) {
+    sp<Client> client;
+    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
+                           LAYER_FLAGS, metadata);
+    return new BufferStateLayer(args);
+}
+
+void TunnelModeEnabledReporterTest::setupScheduler() {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread));
+}
+
+namespace {
+
+TEST_F(TunnelModeEnabledReporterTest, callsAddedListeners) {
+    mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
+
+    bool expectedTunnelModeEnabled = false;
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(expectedTunnelModeEnabled);
+    EXPECT_EQ(expectedTunnelModeEnabled, mTunnelModeEnabledListener->mTunnelModeEnabled);
+
+    expectedTunnelModeEnabled = true;
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(expectedTunnelModeEnabled);
+    EXPECT_EQ(expectedTunnelModeEnabled, mTunnelModeEnabledListener->mTunnelModeEnabled);
+
+    mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener);
+
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false);
+    EXPECT_EQ(expectedTunnelModeEnabled, mTunnelModeEnabledListener->mTunnelModeEnabled);
+}
+
+TEST_F(TunnelModeEnabledReporterTest, callsNewListenerImmediately) {
+    bool expectedTunnelModeEnabled = false;
+    mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(expectedTunnelModeEnabled);
+
+    mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
+    EXPECT_EQ(expectedTunnelModeEnabled, mTunnelModeEnabledListener->mTunnelModeEnabled);
+}
+
+TEST_F(TunnelModeEnabledReporterTest, callsNewListenerWithFreshInformation) {
+    sp<Layer> layer = createBufferStateLayer();
+    sp<NativeHandle> stream =
+            NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
+                                 false);
+    mFlinger.setLayerSidebandStream(layer, stream);
+    mFlinger.mutableCurrentState().layersSortedByZ.add(layer);
+    mTunnelModeEnabledReporter->updateTunnelModeStatus();
+    mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
+    EXPECT_EQ(true, mTunnelModeEnabledListener->mTunnelModeEnabled);
+    mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener);
+
+    mFlinger.mutableCurrentState().layersSortedByZ.remove(layer);
+    mTunnelModeEnabledReporter->updateTunnelModeStatus();
+    mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
+    EXPECT_EQ(false, mTunnelModeEnabledListener->mTunnelModeEnabled);
+}
+
+TEST_F(TunnelModeEnabledReporterTest, layerWithSidebandStreamTriggersUpdate) {
+    mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
+    EXPECT_EQ(false, mTunnelModeEnabledListener->mTunnelModeEnabled);
+
+    sp<Layer> simpleLayer = createBufferStateLayer();
+    sp<Layer> layerWithSidebandStream = createBufferStateLayer();
+    sp<NativeHandle> stream =
+            NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
+                                 false);
+    mFlinger.setLayerSidebandStream(layerWithSidebandStream, stream);
+
+    mFlinger.mutableCurrentState().layersSortedByZ.add(simpleLayer);
+    mFlinger.mutableCurrentState().layersSortedByZ.add(layerWithSidebandStream);
+    mTunnelModeEnabledReporter->updateTunnelModeStatus();
+    EXPECT_EQ(true, mTunnelModeEnabledListener->mTunnelModeEnabled);
+
+    mFlinger.mutableCurrentState().layersSortedByZ.remove(layerWithSidebandStream);
+    mTunnelModeEnabledReporter->updateTunnelModeStatus();
+    EXPECT_EQ(false, mTunnelModeEnabledListener->mTunnelModeEnabled);
+}
+
+} // namespace
+} // namespace android