Merge "Add error logging when channel receives an invalid message" into sc-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 307e21c..4b64203 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -60,9 +60,6 @@
       ]
     },
     {
-      "name": "libsurfaceflinger_unittest"
-    },
-    {
       "name": "CtsGraphicsTestCases",
       "options": [
         {
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index a546236..3f180d9 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -47,6 +47,9 @@
         "libutils",
         "server_configurable_flags",
     ],
+    static_libs: [
+        "libasync_safe",
+    ],
     export_shared_lib_headers: [
         "libbinder",
     ],
@@ -250,6 +253,7 @@
     ],
 
     static_libs: [
+        "libasync_safe",
         "libdiskusage",
         "libotapreoptparameters",
     ],
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 0cf50a3..204953c 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -36,6 +36,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <async_safe/log.h>
 #include <cutils/fs.h>
 #include <cutils/properties.h>
 #include <cutils/sched_policy.h>
@@ -727,7 +728,8 @@
 
         if (flock(out_fd.get(), LOCK_EX | LOCK_NB) != 0) {
             if (errno != EWOULDBLOCK) {
-                PLOG(WARNING) << "Error locking profile " << package_name;
+                async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, "Error locking profile %s: %d",
+                        package_name.c_str(), errno);
             }
             // This implies that the app owning this profile is running
             // (and has acquired the lock).
@@ -735,13 +737,15 @@
             // The app never acquires the lock for the reference profiles of primary apks.
             // Only dex2oat from installd will do that. Since installd is single threaded
             // we should not see this case. Nevertheless be prepared for it.
-            PLOG(WARNING) << "Failed to flock " << package_name;
+            async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, "Failed to flock %s: %d",
+                    package_name.c_str(), errno);
             return false;
         }
 
         bool truncated = ftruncate(out_fd.get(), 0) == 0;
         if (!truncated) {
-            PLOG(WARNING) << "Could not truncate " << package_name;
+            async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, "Could not truncate %s: %d",
+                    package_name.c_str(), errno);
         }
 
         // Copy over data.
@@ -755,7 +759,8 @@
             write(out_fd.get(), buffer, bytes);
         }
         if (flock(out_fd.get(), LOCK_UN) != 0) {
-            PLOG(WARNING) << "Error unlocking profile " << package_name;
+            async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, "Error unlocking profile %s: %d",
+                    package_name.c_str(), errno);
         }
         // Use _exit since we don't want to run the global destructors in the child.
         // b/62597429
@@ -1513,7 +1518,8 @@
 
         // Validate the path structure.
         if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid, uid, storage_flag)) {
-            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                    "Could not validate secondary dex path %s", dex_path.c_str());
             _exit(kSecondaryDexDexoptAnalyzerSkippedValidatePath);
         }
 
@@ -1809,7 +1815,8 @@
         drop_capabilities(uid);
 
         if (flock(out_oat.fd(), LOCK_EX | LOCK_NB) != 0) {
-            PLOG(ERROR) << "flock(" << out_oat.path() << ") failed";
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "flock(%s) failed",
+                    out_oat.path().c_str());
             _exit(DexoptReturnCodes::kFlock);
         }
 
@@ -1904,7 +1911,8 @@
         const char* volume_uuid_cstr = volume_uuid ? volume_uuid->c_str() : nullptr;
         if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid_cstr,
                 uid, storage_flag)) {
-            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                    "Could not validate secondary dex path %s", dex_path.c_str());
             _exit(kReconcileSecondaryDexValidationError);
         }
 
@@ -1917,7 +1925,8 @@
             case kSecondaryDexAccessIOError: _exit(kReconcileSecondaryDexAccessIOError);
             case kSecondaryDexAccessPermissionError: _exit(kReconcileSecondaryDexValidationError);
             default:
-                LOG(ERROR) << "Unexpected result from check_secondary_dex_access: " << access_check;
+                async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                        "Unexpected result from check_secondary_dex_access: %d", access_check);
                 _exit(kReconcileSecondaryDexValidationError);
         }
 
@@ -1930,7 +1939,7 @@
             std::string error_msg;
             if (!create_secondary_dex_oat_layout(
                     dex_path,isas[i], oat_dir, oat_isa_dir, oat_path, &error_msg)) {
-                LOG(ERROR) << error_msg;
+                async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "%s", error_msg.c_str());
                 _exit(kReconcileSecondaryDexValidationError);
             }
 
@@ -1957,7 +1966,8 @@
             result = rmdir_if_empty(oat_dir) && result;
         }
         if (!result) {
-            PLOG(ERROR) << "Failed to clean secondary dex artifacts for location " << dex_path;
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                    "Could not validate secondary dex path %s", dex_path.c_str());
         }
         _exit(result ? kReconcileSecondaryDexCleanedUp : kReconcileSecondaryDexAccessIOError);
     }
@@ -2030,7 +2040,8 @@
         pipe_read.reset();
 
         if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid_cstr, uid, storage_flag)) {
-            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                    "Could not validate secondary dex path %s", dex_path.c_str());
             _exit(DexoptReturnCodes::kHashValidatePath);
         }
 
@@ -2041,6 +2052,8 @@
                 _exit(0);
             }
             PLOG(ERROR) << "Failed to open secondary dex " << dex_path;
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                    "Failed to open secondary dex %s: %d", dex_path.c_str(), errno);
             _exit(DexoptReturnCodes::kHashOpenPath);
         }
 
@@ -2053,7 +2066,8 @@
             if (bytes_read == 0) {
                 break;
             } else if (bytes_read == -1) {
-                PLOG(ERROR) << "Failed to read secondary dex " << dex_path;
+                async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                        "Failed to read secondary dex %s: %d", dex_path.c_str(), errno);
                 _exit(DexoptReturnCodes::kHashReadDex);
             }
 
diff --git a/cmds/installd/file_parsing.h b/cmds/installd/file_parsing.h
index 3e2f815..88801ca 100644
--- a/cmds/installd/file_parsing.h
+++ b/cmds/installd/file_parsing.h
@@ -19,18 +19,14 @@
 
 #include <fstream>
 #include <functional>
-#include <string>
+#include <string_view>
+#include "android-base/unique_fd.h"
 
 namespace android {
 namespace installd {
 
-bool ParseFile(const std::string& strFile, std::function<bool (const std::string&)> parse) {
-    std::ifstream input_stream(strFile);
-
-    if (!input_stream.is_open()) {
-        return false;
-    }
-
+template<typename Func>
+bool ParseFile(std::istream& input_stream, Func parse) {
     while (!input_stream.eof()) {
         // Read the next line.
         std::string line;
@@ -54,6 +50,15 @@
     return true;
 }
 
+template<typename Func>
+bool ParseFile(std::string_view str_file, Func parse) {
+  std::ifstream ifs(str_file);
+  if (!ifs.is_open()) {
+    return false;
+  }
+  return ParseFile(ifs, parse);
+}
+
 }  // namespace installd
 }  // namespace android
 
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index ed31ad9..6aa32b8 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -26,6 +26,7 @@
 #include <sys/capability.h>
 #include <sys/prctl.h>
 #include <sys/stat.h>
+#include <sys/mman.h>
 
 #include <android-base/logging.h>
 #include <android-base/macros.h>
@@ -36,6 +37,7 @@
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 
+#include "android-base/file.h"
 #include "dexopt.h"
 #include "file_parsing.h"
 #include "globals.h"
@@ -195,38 +197,63 @@
         //   export NAME VALUE
         // For simplicity, don't respect string quotation. The values we are interested in can be
         // encoded without them.
-        // init.environ.rc and etc/classpath have the same format for
-        // environment variable exports and can be matched by the same regex.
+        //
+        // init.environ.rc and derive_classpath all have the same format for
+        // environment variable exports (since they are all meant to be read by
+        // init) and can be matched by the same regex.
+
+        std::regex export_regex("\\s*export\\s+(\\S+)\\s+(\\S+)");
+        auto parse_results = [&](auto& input) {
+          ParseFile(input, [&](const std::string& line) {
+              std::smatch export_match;
+              if (!std::regex_match(line, export_match, export_regex)) {
+                  return true;
+              }
+
+              if (export_match.size() != 3) {
+                  return true;
+              }
+
+              std::string name = export_match[1].str();
+              std::string value = export_match[2].str();
+
+              system_properties_.SetProperty(name, value);
+
+              return true;
+          });
+        };
+
         // TODO Just like with the system-properties above we really should have
         // common code between init and otapreopt to deal with reading these
         // things. See b/181182967
+        // There have been a variety of places the various env-vars have been
+        // over the years.  Expand or reduce this list as needed.
         static constexpr const char* kEnvironmentVariableSources[] = {
-                "/init.environ.rc", "/etc/classpath"
+                "/init.environ.rc",
         };
-
-        std::regex export_regex("\\s*export\\s+(\\S+)\\s+(\\S+)");
+        // First get everything from the static files.
         for (const char* env_vars_file : kEnvironmentVariableSources) {
-            bool parse_result = ParseFile(env_vars_file, [&](const std::string& line) {
-                std::smatch export_match;
-                if (!std::regex_match(line, export_match, export_regex)) {
-                    return true;
-                }
-
-                if (export_match.size() != 3) {
-                    return true;
-                }
-
-                std::string name = export_match[1].str();
-                std::string value = export_match[2].str();
-
-                system_properties_.SetProperty(name, value);
-
-                return true;
-            });
-            if (!parse_result) {
-                return false;
-            }
+          parse_results(env_vars_file);
         }
+
+        // Next get everything from derive_classpath, since we're already in the
+        // chroot it will get the new versions of any dependencies.
+        {
+          android::base::unique_fd fd(memfd_create("derive_classpath_temp", MFD_CLOEXEC));
+          if (!fd.ok()) {
+            LOG(ERROR) << "Unable to create fd for derive_classpath";
+            return false;
+          }
+          std::string memfd_file = StringPrintf("/proc/%d/fd/%d", getpid(), fd.get());
+          std::string error_msg;
+          if (!Exec({"/apex/com.android.sdkext/bin/derive_classpath", memfd_file}, &error_msg)) {
+            PLOG(ERROR) << "Running derive_classpath failed: " << error_msg;
+            return false;
+          }
+          std::ifstream ifs(memfd_file);
+          parse_results(ifs);
+        }
+
         if (system_properties_.GetProperty(kAndroidDataPathPropertyName) == nullptr) {
             return false;
         }
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 83f01de..c62734a 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -275,6 +275,7 @@
     static constexpr const std::string_view kRequiredApexs[] = {
       "com.android.art",
       "com.android.runtime",
+      "com.android.sdkext",  // For derive_classpath
     };
     std::array<bool, arraysize(kRequiredApexs)> found_apexs{ false, false };
     DIR* apex_dir = opendir("/apex");
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index f67ab81..7082017 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -20,6 +20,7 @@
         "libcutils",
     ],
     static_libs: [
+        "libasync_safe",
         "libdiskusage",
         "libinstalld",
         "liblog",
@@ -44,6 +45,7 @@
         "server_configurable_flags",
     ],
     static_libs: [
+        "libasync_safe",
         "libdiskusage",
         "libinstalld",
         "liblog",
@@ -84,6 +86,7 @@
         "server_configurable_flags",
     ],
     static_libs: [
+        "libasync_safe",
         "libdiskusage",
         "libinstalld",
         "liblog",
@@ -124,6 +127,7 @@
         "server_configurable_flags",
     ],
     static_libs: [
+        "libasync_safe",
         "libdiskusage",
         "libinstalld",
         "liblog",
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index b429fb3..90db509 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -481,7 +481,12 @@
           name.c_str());
 
     std::thread([=] {
-        (void)base::SetProperty("ctl.interface_start", "aidl/" + name);
+        if (!base::SetProperty("ctl.interface_start", "aidl/" + name)) {
+            LOG(INFO) << "Tried to start aidl service " << name
+                      << " as a lazy service, but was unable to. Usually this happens when a "
+                         "service is not installed, but if the service is intended to be used as a "
+                         "lazy service, then it may be configured incorrectly.";
+        }
     }).detach();
 }
 
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index f6c2e55..9881371 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -526,6 +526,9 @@
  * callback timings, and changes to the time interval at which the system releases buffers back to
  * the application.
  *
+ * You can register for changes in the refresh rate using
+ * \a AChoreographer_registerRefreshRateCallback.
+ *
  * \param frameRate is the intended frame rate of this surface, in frames per second. 0 is a special
  * value that indicates the app will accept the system's choice for the display frame rate, which is
  * the default behavior if this function isn't called. The frameRate param does <em>not</em> need to
diff --git a/include/input/Input.h b/include/input/Input.h
index bb5ca0e..7b522bb 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -318,6 +318,12 @@
  */
 constexpr float AMOTION_EVENT_INVALID_CURSOR_POSITION = std::numeric_limits<float>::quiet_NaN();
 
+/**
+ * Invalid value for display size. Used when display size isn't available for an event or doesn't
+ * matter. This is just a constant 0 so that it has no effect if unused.
+ */
+constexpr int32_t AMOTION_EVENT_INVALID_DISPLAY_SIZE = 0;
+
 /*
  * Pointer coordinate data.
  */
@@ -360,6 +366,8 @@
         return getAxisValue(AMOTION_EVENT_AXIS_Y);
     }
 
+    vec2 getXYValue() const { return vec2(getX(), getY()); }
+
 #ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
@@ -548,6 +556,8 @@
 
     void setCursorPosition(float x, float y);
 
+    int2 getDisplaySize() const { return {mDisplayWidth, mDisplayHeight}; }
+
     static inline bool isValidCursorPosition(float x, float y) { return !isnan(x) && !isnan(y); }
 
     inline nsecs_t getDownTime() const { return mDownTime; }
@@ -570,8 +580,17 @@
 
     inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
 
+    /**
+     * The actual raw pointer coords: whatever comes from the input device without any external
+     * transforms applied.
+     */
     const PointerCoords* getRawPointerCoords(size_t pointerIndex) const;
 
+    /**
+     * This is the raw axis value. However, for X/Y axes, this currently applies a "compat-raw"
+     * transform because many apps (incorrectly) assumed that raw == oriented-screen-space.
+     * "compat raw" is raw coordinates with screen rotation applied.
+     */
     float getRawAxisValue(int32_t axis, size_t pointerIndex) const;
 
     inline float getRawX(size_t pointerIndex) const {
@@ -634,9 +653,18 @@
         return mSampleEventTimes[historicalIndex];
     }
 
+    /**
+     * The actual raw pointer coords: whatever comes from the input device without any external
+     * transforms applied.
+     */
     const PointerCoords* getHistoricalRawPointerCoords(
             size_t pointerIndex, size_t historicalIndex) const;
 
+    /**
+     * This is the raw axis value. However, for X/Y axes, this currently applies a "compat-raw"
+     * transform because many apps (incorrectly) assumed that raw == oriented-screen-space.
+     * "compat raw" is raw coordinates with screen rotation applied.
+     */
     float getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
             size_t historicalIndex) const;
 
@@ -704,9 +732,9 @@
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
                     float xPrecision, float yPrecision, float rawXCursorPosition,
-                    float rawYCursorPosition, nsecs_t downTime, nsecs_t eventTime,
-                    size_t pointerCount, const PointerProperties* pointerProperties,
-                    const PointerCoords* pointerCoords);
+                    float rawYCursorPosition, int32_t displayWidth, int32_t displayHeight,
+                    nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                    const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
 
@@ -759,6 +787,8 @@
     float mYPrecision;
     float mRawXCursorPosition;
     float mRawYCursorPosition;
+    int32_t mDisplayWidth;
+    int32_t mDisplayHeight;
     nsecs_t mDownTime;
     Vector<PointerProperties> mPointerProperties;
     std::vector<nsecs_t> mSampleEventTimes;
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 898d1a9..ff33678 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -136,6 +136,8 @@
             float yPrecision;
             float xCursorPosition;
             float yCursorPosition;
+            int32_t displayWidth;
+            int32_t displayHeight;
             uint32_t pointerCount;
             uint32_t empty3;
             /**
@@ -353,8 +355,9 @@
                                 int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
-                                float yCursorPosition, nsecs_t downTime, nsecs_t eventTime,
-                                uint32_t pointerCount, const PointerProperties* pointerProperties,
+                                float yCursorPosition, int32_t displayWidth, int32_t displayHeight,
+                                nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
+                                const PointerProperties* pointerProperties,
                                 const PointerCoords* pointerCoords);
 
     /* Publishes a focus event to the input channel.
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index 36097d6..121be6d 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -168,6 +168,10 @@
     // Transform applied to individual windows.
     ui::Transform transform;
 
+    // Display size in its natural rotation. Used to rotate raw coordinates for compatibility.
+    int32_t displayWidth = AMOTION_EVENT_INVALID_DISPLAY_SIZE;
+    int32_t displayHeight = AMOTION_EVENT_INVALID_DISPLAY_SIZE;
+
     /*
      * This is filled in by the WM relative to the frame and then translated
      * to absolute coordinates by SurfaceFlinger once the frame is computed.
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index d964d25..d5bdd1c 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -29,6 +29,16 @@
 
 namespace android {
 
+// Service implementations inherit from BBinder and IBinder, and this is frozen
+// in prebuilts.
+#ifdef __LP64__
+static_assert(sizeof(IBinder) == 24);
+static_assert(sizeof(BBinder) == 40);
+#else
+static_assert(sizeof(IBinder) == 12);
+static_assert(sizeof(BBinder) == 20);
+#endif
+
 // ---------------------------------------------------------------------------
 
 IBinder::IBinder()
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6fb1227..ef7fd44 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -29,8 +29,6 @@
 #include <utils/SystemClock.h>
 #include <utils/threads.h>
 
-#include <private/binder/binder_module.h>
-
 #include <atomic>
 #include <errno.h>
 #include <inttypes.h>
@@ -43,6 +41,7 @@
 #include <unistd.h>
 
 #include "Static.h"
+#include "binder_module.h"
 
 #if LOG_NDEBUG
 
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index a735309..5627a78 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -48,10 +48,10 @@
 #include <utils/String8.h>
 #include <utils/misc.h>
 
-#include <private/binder/binder_module.h>
 #include "RpcState.h"
 #include "Static.h"
 #include "Utils.h"
+#include "binder_module.h"
 
 #define LOG_REFS(...)
 //#define LOG_REFS(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
@@ -78,7 +78,11 @@
 namespace android {
 
 // many things compile this into prebuilts on the stack
-static_assert(sizeof(Parcel) == 60 || sizeof(Parcel) == 120);
+#ifdef __LP64__
+static_assert(sizeof(Parcel) == 120);
+#else
+static_assert(sizeof(Parcel) == 60);
+#endif
 
 static std::atomic<size_t> gParcelGlobalAllocCount;
 static std::atomic<size_t> gParcelGlobalAllocSize;
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 1d3beb4..4fd0dc7 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -27,8 +27,8 @@
 #include <utils/String8.h>
 #include <utils/threads.h>
 
-#include <private/binder/binder_module.h>
 #include "Static.h"
+#include "binder_module.h"
 
 #include <errno.h>
 #include <fcntl.h>
diff --git a/libs/binder/RpcConnection.cpp b/libs/binder/RpcConnection.cpp
index f2302f7..4b3a53f 100644
--- a/libs/binder/RpcConnection.cpp
+++ b/libs/binder/RpcConnection.cpp
@@ -18,12 +18,7 @@
 
 #include <binder/RpcConnection.h>
 
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
+#include <inttypes.h>
 #include <unistd.h>
 
 #include <string_view>
@@ -32,6 +27,7 @@
 #include <binder/Stability.h>
 #include <utils/String8.h>
 
+#include "RpcSocketAddress.h"
 #include "RpcState.h"
 #include "RpcWireFormat.h"
 
@@ -39,16 +35,9 @@
 extern "C" pid_t gettid();
 #endif
 
-#ifdef __BIONIC__
-#include <linux/vm_sockets.h>
-#endif
-
 namespace android {
 
 using base::unique_fd;
-using AddrInfo = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>;
-
-RpcConnection::SocketAddress::~SocketAddress() {}
 
 RpcConnection::RpcConnection() {
     LOG_RPC_DETAIL("RpcConnection created %p", this);
@@ -67,125 +56,24 @@
     return sp<RpcConnection>::make();
 }
 
-class UnixSocketAddress : public RpcConnection::SocketAddress {
-public:
-    explicit UnixSocketAddress(const char* path) : mAddr({.sun_family = AF_UNIX}) {
-        unsigned int pathLen = strlen(path) + 1;
-        LOG_ALWAYS_FATAL_IF(pathLen > sizeof(mAddr.sun_path), "Socket path is too long: %u %s",
-                            pathLen, path);
-        memcpy(mAddr.sun_path, path, pathLen);
-    }
-    virtual ~UnixSocketAddress() {}
-    std::string toString() const override {
-        return String8::format("path '%.*s'", static_cast<int>(sizeof(mAddr.sun_path)),
-                               mAddr.sun_path)
-                .c_str();
-    }
-    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
-    size_t addrSize() const override { return sizeof(mAddr); }
-
-private:
-    sockaddr_un mAddr;
-};
-
-bool RpcConnection::setupUnixDomainServer(const char* path) {
-    return setupSocketServer(UnixSocketAddress(path));
-}
-
-bool RpcConnection::addUnixDomainClient(const char* path) {
-    return addSocketClient(UnixSocketAddress(path));
+bool RpcConnection::setupUnixDomainClient(const char* path) {
+    return setupSocketClient(UnixSocketAddress(path));
 }
 
 #ifdef __BIONIC__
 
-class VsockSocketAddress : public RpcConnection::SocketAddress {
-public:
-    VsockSocketAddress(unsigned int cid, unsigned int port)
-          : mAddr({
-                    .svm_family = AF_VSOCK,
-                    .svm_port = port,
-                    .svm_cid = cid,
-            }) {}
-    virtual ~VsockSocketAddress() {}
-    std::string toString() const override {
-        return String8::format("cid %u port %u", mAddr.svm_cid, mAddr.svm_port).c_str();
-    }
-    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
-    size_t addrSize() const override { return sizeof(mAddr); }
-
-private:
-    sockaddr_vm mAddr;
-};
-
-bool RpcConnection::setupVsockServer(unsigned int port) {
-    // realizing value w/ this type at compile time to avoid ubsan abort
-    constexpr unsigned int kAnyCid = VMADDR_CID_ANY;
-
-    return setupSocketServer(VsockSocketAddress(kAnyCid, port));
-}
-
-bool RpcConnection::addVsockClient(unsigned int cid, unsigned int port) {
-    return addSocketClient(VsockSocketAddress(cid, port));
+bool RpcConnection::setupVsockClient(unsigned int cid, unsigned int port) {
+    return setupSocketClient(VsockSocketAddress(cid, port));
 }
 
 #endif // __BIONIC__
 
-class InetSocketAddress : public RpcConnection::SocketAddress {
-public:
-    InetSocketAddress(const sockaddr* sockAddr, size_t size, const char* addr, unsigned int port)
-          : mSockAddr(sockAddr), mSize(size), mAddr(addr), mPort(port) {}
-    [[nodiscard]] std::string toString() const override {
-        return String8::format("%s:%u", mAddr, mPort).c_str();
-    }
-    [[nodiscard]] const sockaddr* addr() const override { return mSockAddr; }
-    [[nodiscard]] size_t addrSize() const override { return mSize; }
-
-private:
-    const sockaddr* mSockAddr;
-    size_t mSize;
-    const char* mAddr;
-    unsigned int mPort;
-};
-
-AddrInfo GetAddrInfo(const char* addr, unsigned int port) {
-    addrinfo hint{
-            .ai_flags = 0,
-            .ai_family = AF_UNSPEC,
-            .ai_socktype = SOCK_STREAM,
-            .ai_protocol = 0,
-    };
-    addrinfo* aiStart = nullptr;
-    if (int rc = getaddrinfo(addr, std::to_string(port).data(), &hint, &aiStart); 0 != rc) {
-        ALOGE("Unable to resolve %s:%u: %s", addr, port, gai_strerror(rc));
-        return AddrInfo(nullptr, nullptr);
-    }
-    if (aiStart == nullptr) {
-        ALOGE("Unable to resolve %s:%u: getaddrinfo returns null", addr, port);
-        return AddrInfo(nullptr, nullptr);
-    }
-    return AddrInfo(aiStart, &freeaddrinfo);
-}
-
-bool RpcConnection::setupInetServer(unsigned int port) {
-    const char* kAddr = "127.0.0.1";
-
-    auto aiStart = GetAddrInfo(kAddr, port);
-    if (aiStart == nullptr) return false;
-    for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
-        InetSocketAddress socketAddress(ai->ai_addr, ai->ai_addrlen, kAddr, port);
-        if (setupSocketServer(socketAddress)) return true;
-    }
-    ALOGE("None of the socket address resolved for %s:%u can be set up as inet server.", kAddr,
-          port);
-    return false;
-}
-
-bool RpcConnection::addInetClient(const char* addr, unsigned int port) {
-    auto aiStart = GetAddrInfo(addr, port);
+bool RpcConnection::setupInetClient(const char* addr, unsigned int port) {
+    auto aiStart = InetSocketAddress::getAddrInfo(addr, port);
     if (aiStart == nullptr) return false;
     for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
         InetSocketAddress socketAddress(ai->ai_addr, ai->ai_addrlen, addr, port);
-        if (addSocketClient(socketAddress)) return true;
+        if (setupSocketClient(socketAddress)) return true;
     }
     ALOGE("None of the socket address resolved for %s:%u can be added as inet client.", addr, port);
     return false;
@@ -208,6 +96,11 @@
     return state()->getRootObject(socket.fd(), sp<RpcConnection>::fromExisting(this));
 }
 
+status_t RpcConnection::getMaxThreads(size_t* maxThreads) {
+    ExclusiveSocket socket(sp<RpcConnection>::fromExisting(this), SocketUse::CLIENT);
+    return state()->getMaxThreads(socket.fd(), sp<RpcConnection>::fromExisting(this), maxThreads);
+}
+
 status_t RpcConnection::transact(const RpcAddress& address, uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags) {
     ExclusiveSocket socket(sp<RpcConnection>::fromExisting(this),
@@ -222,23 +115,43 @@
     return state()->sendDecStrong(socket.fd(), address);
 }
 
-void RpcConnection::join() {
-    // TODO(b/185167543): do this dynamically, instead of from a static number
-    // of threads
-    unique_fd clientFd(
-            TEMP_FAILURE_RETRY(accept4(mServer.get(), nullptr, 0 /*length*/, SOCK_CLOEXEC)));
-    if (clientFd < 0) {
-        // If this log becomes confusing, should save more state from setupUnixDomainServer
-        // in order to output here.
-        ALOGE("Could not accept4 socket: %s", strerror(errno));
-        return;
+status_t RpcConnection::readId() {
+    {
+        std::lock_guard<std::mutex> _l(mSocketMutex);
+        LOG_ALWAYS_FATAL_IF(mForServer != nullptr, "Can only update ID for client.");
     }
 
-    LOG_RPC_DETAIL("accept4 on fd %d yields fd %d", mServer.get(), clientFd.get());
+    int32_t id;
 
+    ExclusiveSocket socket(sp<RpcConnection>::fromExisting(this), SocketUse::CLIENT);
+    status_t status =
+            state()->getConnectionId(socket.fd(), sp<RpcConnection>::fromExisting(this), &id);
+    if (status != OK) return status;
+
+    LOG_RPC_DETAIL("RpcConnection %p has id %d", this, id);
+    mId = id;
+    return OK;
+}
+
+void RpcConnection::startThread(unique_fd client) {
+    std::lock_guard<std::mutex> _l(mSocketMutex);
+    sp<RpcConnection> holdThis = sp<RpcConnection>::fromExisting(this);
+    int fd = client.release();
+    auto thread = std::thread([=] {
+        holdThis->join(unique_fd(fd));
+        {
+            std::lock_guard<std::mutex> _l(holdThis->mSocketMutex);
+            size_t erased = mThreads.erase(std::this_thread::get_id());
+            LOG_ALWAYS_FATAL_IF(erased != 0, "Could not erase thread.");
+        }
+    });
+    mThreads[thread.get_id()] = std::move(thread);
+}
+
+void RpcConnection::join(unique_fd client) {
     // must be registered to allow arbitrary client code executing commands to
     // be able to do nested calls (we can't only read from it)
-    sp<ConnectionSocket> socket = assignServerToThisThread(std::move(clientFd));
+    sp<ConnectionSocket> socket = assignServerToThisThread(std::move(client));
 
     while (true) {
         status_t error =
@@ -254,41 +167,49 @@
                         "bad state: socket object guaranteed to be in list");
 }
 
-void RpcConnection::setForServer(const wp<RpcServer>& server) {
-    mForServer = server;
-}
-
 wp<RpcServer> RpcConnection::server() {
     return mForServer;
 }
 
-bool RpcConnection::setupSocketServer(const SocketAddress& addr) {
-    LOG_ALWAYS_FATAL_IF(mServer.get() != -1, "Each RpcConnection can only have one server.");
+bool RpcConnection::setupSocketClient(const RpcSocketAddress& addr) {
+    {
+        std::lock_guard<std::mutex> _l(mSocketMutex);
+        LOG_ALWAYS_FATAL_IF(mClients.size() != 0,
+                            "Must only setup connection once, but already has %zu clients",
+                            mClients.size());
+    }
 
-    unique_fd serverFd(
-            TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
-    if (serverFd == -1) {
-        ALOGE("Could not create socket: %s", strerror(errno));
+    if (!setupOneSocketClient(addr, RPC_CONNECTION_ID_NEW)) return false;
+
+    // TODO(b/185167543): we should add additional connections dynamically
+    // instead of all at once.
+    // TODO(b/186470974): first risk of blocking
+    size_t numThreadsAvailable;
+    if (status_t status = getMaxThreads(&numThreadsAvailable); status != OK) {
+        ALOGE("Could not get max threads after initial connection to %s: %s",
+              addr.toString().c_str(), statusToString(status).c_str());
         return false;
     }
 
-    if (0 != TEMP_FAILURE_RETRY(bind(serverFd.get(), addr.addr(), addr.addrSize()))) {
-        int savedErrno = errno;
-        ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+    if (status_t status = readId(); status != OK) {
+        ALOGE("Could not get connection id after initial connection to %s; %s",
+              addr.toString().c_str(), statusToString(status).c_str());
         return false;
     }
 
-    if (0 != TEMP_FAILURE_RETRY(listen(serverFd.get(), 1 /*backlog*/))) {
-        int savedErrno = errno;
-        ALOGE("Could not listen socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
-        return false;
+    // we've already setup one client
+    for (size_t i = 0; i + 1 < numThreadsAvailable; i++) {
+        // TODO(b/185167543): avoid race w/ accept4 not being called on server
+        for (size_t tries = 0; tries < 5; tries++) {
+            if (setupOneSocketClient(addr, mId.value())) break;
+            usleep(10000);
+        }
     }
 
-    mServer = std::move(serverFd);
     return true;
 }
 
-bool RpcConnection::addSocketClient(const SocketAddress& addr) {
+bool RpcConnection::setupOneSocketClient(const RpcSocketAddress& addr, int32_t id) {
     unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
     if (serverFd == -1) {
@@ -303,20 +224,32 @@
         return false;
     }
 
+    if (sizeof(id) != TEMP_FAILURE_RETRY(write(serverFd.get(), &id, sizeof(id)))) {
+        int savedErrno = errno;
+        ALOGE("Could not write id to socket at %s: %s", addr.toString().c_str(),
+              strerror(savedErrno));
+        return false;
+    }
+
     LOG_RPC_DETAIL("Socket at %s client with fd %d", addr.toString().c_str(), serverFd.get());
 
     addClient(std::move(serverFd));
     return true;
 }
 
-void RpcConnection::addClient(unique_fd&& fd) {
+void RpcConnection::addClient(unique_fd fd) {
     std::lock_guard<std::mutex> _l(mSocketMutex);
     sp<ConnectionSocket> connection = sp<ConnectionSocket>::make();
     connection->fd = std::move(fd);
     mClients.push_back(connection);
 }
 
-sp<RpcConnection::ConnectionSocket> RpcConnection::assignServerToThisThread(unique_fd&& fd) {
+void RpcConnection::setForServer(const wp<RpcServer>& server, int32_t connectionId) {
+    mId = connectionId;
+    mForServer = server;
+}
+
+sp<RpcConnection::ConnectionSocket> RpcConnection::assignServerToThisThread(unique_fd fd) {
     std::lock_guard<std::mutex> _l(mSocketMutex);
     sp<ConnectionSocket> connection = sp<ConnectionSocket>::make();
     connection->fd = std::move(fd);
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 9a0be92..de7160e 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -19,6 +19,7 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 
+#include <thread>
 #include <vector>
 
 #include <binder/Parcel.h>
@@ -26,10 +27,13 @@
 #include <log/log.h>
 #include "RpcState.h"
 
+#include "RpcSocketAddress.h"
 #include "RpcWireFormat.h"
 
 namespace android {
 
+using base::unique_fd;
+
 RpcServer::RpcServer() {}
 RpcServer::~RpcServer() {}
 
@@ -41,16 +45,68 @@
     mAgreedExperimental = true;
 }
 
-sp<RpcConnection> RpcServer::addClientConnection() {
-    LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
+bool RpcServer::setupUnixDomainServer(const char* path) {
+    return setupSocketServer(UnixSocketAddress(path));
+}
 
-    auto connection = RpcConnection::make();
-    connection->setForServer(sp<RpcServer>::fromExisting(this));
-    {
-        std::lock_guard<std::mutex> _l(mLock);
-        mConnections.push_back(connection);
+#ifdef __BIONIC__
+
+bool RpcServer::setupVsockServer(unsigned int port) {
+    // realizing value w/ this type at compile time to avoid ubsan abort
+    constexpr unsigned int kAnyCid = VMADDR_CID_ANY;
+
+    return setupSocketServer(VsockSocketAddress(kAnyCid, port));
+}
+
+#endif // __BIONIC__
+
+bool RpcServer::setupInetServer(unsigned int port, unsigned int* assignedPort) {
+    const char* kAddr = "127.0.0.1";
+
+    if (assignedPort != nullptr) *assignedPort = 0;
+    auto aiStart = InetSocketAddress::getAddrInfo(kAddr, port);
+    if (aiStart == nullptr) return false;
+    for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
+        InetSocketAddress socketAddress(ai->ai_addr, ai->ai_addrlen, kAddr, port);
+        if (!setupSocketServer(socketAddress)) {
+            continue;
+        }
+
+        LOG_ALWAYS_FATAL_IF(socketAddress.addr()->sa_family != AF_INET, "expecting inet");
+        sockaddr_in addr{};
+        socklen_t len = sizeof(addr);
+        if (0 != getsockname(mServer.get(), reinterpret_cast<sockaddr*>(&addr), &len)) {
+            int savedErrno = errno;
+            ALOGE("Could not getsockname at %s: %s", socketAddress.toString().c_str(),
+                  strerror(savedErrno));
+            return false;
+        }
+        LOG_ALWAYS_FATAL_IF(len != sizeof(addr), "Wrong socket type: len %zu vs len %zu",
+                            static_cast<size_t>(len), sizeof(addr));
+        unsigned int realPort = ntohs(addr.sin_port);
+        LOG_ALWAYS_FATAL_IF(port != 0 && realPort != port,
+                            "Requesting inet server on %s but it is set up on %u.",
+                            socketAddress.toString().c_str(), realPort);
+
+        if (assignedPort != nullptr) {
+            *assignedPort = realPort;
+        }
+
+        return true;
     }
-    return connection;
+    ALOGE("None of the socket address resolved for %s:%u can be set up as inet server.", kAddr,
+          port);
+    return false;
+}
+
+void RpcServer::setMaxThreads(size_t threads) {
+    LOG_ALWAYS_FATAL_IF(threads <= 0, "RpcServer is useless without threads");
+    LOG_ALWAYS_FATAL_IF(mStarted, "must be called before started");
+    mMaxThreads = threads;
+}
+
+size_t RpcServer::getMaxThreads() {
+    return mMaxThreads;
 }
 
 void RpcServer::setRootObject(const sp<IBinder>& binder) {
@@ -63,4 +119,101 @@
     return mRootObject;
 }
 
+void RpcServer::join() {
+    LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
+
+    std::vector<std::thread> pool;
+    {
+        std::lock_guard<std::mutex> _l(mLock);
+        LOG_ALWAYS_FATAL_IF(mServer.get() == -1, "RpcServer must be setup to join.");
+    }
+
+    while (true) {
+        unique_fd clientFd(
+                TEMP_FAILURE_RETRY(accept4(mServer.get(), nullptr, 0 /*length*/, SOCK_CLOEXEC)));
+
+        if (clientFd < 0) {
+            ALOGE("Could not accept4 socket: %s", strerror(errno));
+            continue;
+        }
+        LOG_RPC_DETAIL("accept4 on fd %d yields fd %d", mServer.get(), clientFd.get());
+
+        // TODO(b/183988761): cannot trust this simple ID
+        LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
+        int32_t id;
+        if (sizeof(id) != read(clientFd.get(), &id, sizeof(id))) {
+            ALOGE("Could not read ID from fd %d", clientFd.get());
+            continue;
+        }
+
+        {
+            std::lock_guard<std::mutex> _l(mLock);
+
+            sp<RpcConnection> connection;
+            if (id == RPC_CONNECTION_ID_NEW) {
+                // new client!
+                LOG_ALWAYS_FATAL_IF(mConnectionIdCounter >= INT32_MAX, "Out of connection IDs");
+                mConnectionIdCounter++;
+
+                connection = RpcConnection::make();
+                connection->setForServer(wp<RpcServer>::fromExisting(this), mConnectionIdCounter);
+
+                mConnections[mConnectionIdCounter] = connection;
+            } else {
+                auto it = mConnections.find(id);
+                if (it == mConnections.end()) {
+                    ALOGE("Cannot add thread, no record of connection with ID %d", id);
+                    continue;
+                }
+                connection = it->second;
+            }
+
+            connection->startThread(std::move(clientFd));
+        }
+    }
+}
+
+std::vector<sp<RpcConnection>> RpcServer::listConnections() {
+    std::lock_guard<std::mutex> _l(mLock);
+    std::vector<sp<RpcConnection>> connections;
+    for (auto& [id, connection] : mConnections) {
+        (void)id;
+        connections.push_back(connection);
+    }
+    return connections;
+}
+
+bool RpcServer::setupSocketServer(const RpcSocketAddress& addr) {
+    LOG_RPC_DETAIL("Setting up socket server %s", addr.toString().c_str());
+
+    {
+        std::lock_guard<std::mutex> _l(mLock);
+        LOG_ALWAYS_FATAL_IF(mServer.get() != -1, "Each RpcServer can only have one server.");
+    }
+
+    unique_fd serverFd(
+            TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+    if (serverFd == -1) {
+        ALOGE("Could not create socket: %s", strerror(errno));
+        return false;
+    }
+
+    if (0 != TEMP_FAILURE_RETRY(bind(serverFd.get(), addr.addr(), addr.addrSize()))) {
+        int savedErrno = errno;
+        ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    if (0 != TEMP_FAILURE_RETRY(listen(serverFd.get(), 1 /*backlog*/))) {
+        int savedErrno = errno;
+        ALOGE("Could not listen socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    LOG_RPC_DETAIL("Successfully setup socket server %s", addr.toString().c_str());
+
+    mServer = std::move(serverFd);
+    return true;
+}
+
 } // namespace android
diff --git a/libs/binder/RpcSocketAddress.h b/libs/binder/RpcSocketAddress.h
new file mode 100644
index 0000000..c6a06cf
--- /dev/null
+++ b/libs/binder/RpcSocketAddress.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 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 <string>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#ifdef __BIONIC__
+#include <linux/vm_sockets.h>
+#endif
+
+namespace android {
+
+class RpcSocketAddress {
+public:
+    virtual ~RpcSocketAddress() {}
+    virtual std::string toString() const = 0;
+    virtual const sockaddr* addr() const = 0;
+    virtual size_t addrSize() const = 0;
+};
+
+class UnixSocketAddress : public RpcSocketAddress {
+public:
+    explicit UnixSocketAddress(const char* path) : mAddr({.sun_family = AF_UNIX}) {
+        unsigned int pathLen = strlen(path) + 1;
+        LOG_ALWAYS_FATAL_IF(pathLen > sizeof(mAddr.sun_path), "Socket path is too long: %u %s",
+                            pathLen, path);
+        memcpy(mAddr.sun_path, path, pathLen);
+    }
+    virtual ~UnixSocketAddress() {}
+    std::string toString() const override {
+        return String8::format("path '%.*s'", static_cast<int>(sizeof(mAddr.sun_path)),
+                               mAddr.sun_path)
+                .c_str();
+    }
+    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
+    size_t addrSize() const override { return sizeof(mAddr); }
+
+private:
+    sockaddr_un mAddr;
+};
+
+#ifdef __BIONIC__
+
+class VsockSocketAddress : public RpcSocketAddress {
+public:
+    VsockSocketAddress(unsigned int cid, unsigned int port)
+          : mAddr({
+                    .svm_family = AF_VSOCK,
+                    .svm_port = port,
+                    .svm_cid = cid,
+            }) {}
+    virtual ~VsockSocketAddress() {}
+    std::string toString() const override {
+        return String8::format("cid %u port %u", mAddr.svm_cid, mAddr.svm_port).c_str();
+    }
+    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
+    size_t addrSize() const override { return sizeof(mAddr); }
+
+private:
+    sockaddr_vm mAddr;
+};
+
+#endif // __BIONIC__
+
+class InetSocketAddress : public RpcSocketAddress {
+public:
+    InetSocketAddress(const sockaddr* sockAddr, size_t size, const char* addr, unsigned int port)
+          : mSockAddr(sockAddr), mSize(size), mAddr(addr), mPort(port) {}
+    [[nodiscard]] std::string toString() const override {
+        return String8::format("%s:%u", mAddr, mPort).c_str();
+    }
+    [[nodiscard]] const sockaddr* addr() const override { return mSockAddr; }
+    [[nodiscard]] size_t addrSize() const override { return mSize; }
+
+    using AddrInfo = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>;
+    static AddrInfo getAddrInfo(const char* addr, unsigned int port) {
+        addrinfo hint{
+                .ai_flags = 0,
+                .ai_family = AF_UNSPEC,
+                .ai_socktype = SOCK_STREAM,
+                .ai_protocol = 0,
+        };
+        addrinfo* aiStart = nullptr;
+        if (int rc = getaddrinfo(addr, std::to_string(port).data(), &hint, &aiStart); 0 != rc) {
+            ALOGE("Unable to resolve %s:%u: %s", addr, port, gai_strerror(rc));
+            return AddrInfo(nullptr, nullptr);
+        }
+        if (aiStart == nullptr) {
+            ALOGE("Unable to resolve %s:%u: getaddrinfo returns null", addr, port);
+            return AddrInfo(nullptr, nullptr);
+        }
+        return AddrInfo(aiStart, &freeaddrinfo);
+    }
+
+private:
+    const sockaddr* mSockAddr;
+    size_t mSize;
+    const char* mAddr;
+    unsigned int mPort;
+};
+
+} // namespace android
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index d934136..19dea7e 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -248,6 +248,52 @@
     return reply.readStrongBinder();
 }
 
+status_t RpcState::getMaxThreads(const base::unique_fd& fd, const sp<RpcConnection>& connection,
+                                 size_t* maxThreadsOut) {
+    Parcel data;
+    data.markForRpc(connection);
+    Parcel reply;
+
+    status_t status = transact(fd, RpcAddress::zero(), RPC_SPECIAL_TRANSACT_GET_MAX_THREADS, data,
+                               connection, &reply, 0);
+    if (status != OK) {
+        ALOGE("Error getting max threads: %s", statusToString(status).c_str());
+        return status;
+    }
+
+    int32_t maxThreads;
+    status = reply.readInt32(&maxThreads);
+    if (status != OK) return status;
+    if (maxThreads <= 0) {
+        ALOGE("Error invalid max maxThreads: %d", maxThreads);
+        return BAD_VALUE;
+    }
+
+    *maxThreadsOut = maxThreads;
+    return OK;
+}
+
+status_t RpcState::getConnectionId(const base::unique_fd& fd, const sp<RpcConnection>& connection,
+                                   int32_t* connectionIdOut) {
+    Parcel data;
+    data.markForRpc(connection);
+    Parcel reply;
+
+    status_t status = transact(fd, RpcAddress::zero(), RPC_SPECIAL_TRANSACT_GET_CONNECTION_ID, data,
+                               connection, &reply, 0);
+    if (status != OK) {
+        ALOGE("Error getting connection ID: %s", statusToString(status).c_str());
+        return status;
+    }
+
+    int32_t connectionId;
+    status = reply.readInt32(&connectionId);
+    if (status != OK) return status;
+
+    *connectionIdOut = connectionId;
+    return OK;
+}
+
 status_t RpcState::transact(const base::unique_fd& fd, const RpcAddress& address, uint32_t code,
                             const Parcel& data, const sp<RpcConnection>& connection, Parcel* reply,
                             uint32_t flags) {
@@ -516,23 +562,35 @@
             replyStatus = target->transact(transaction->code, data, &reply, transaction->flags);
         } else {
             LOG_RPC_DETAIL("Got special transaction %u", transaction->code);
-            // special case for 'zero' address (special server commands)
-            switch (transaction->code) {
-                case RPC_SPECIAL_TRANSACT_GET_ROOT: {
-                    sp<IBinder> root;
-                    sp<RpcServer> server = connection->server().promote();
-                    if (server) {
-                        root = server->getRootObject();
-                    } else {
-                        ALOGE("Root object requested, but no server attached.");
-                    }
 
-                    replyStatus = reply.writeStrongBinder(root);
-                    break;
+            sp<RpcServer> server = connection->server().promote();
+            if (server) {
+                // special case for 'zero' address (special server commands)
+                switch (transaction->code) {
+                    case RPC_SPECIAL_TRANSACT_GET_ROOT: {
+                        replyStatus = reply.writeStrongBinder(server->getRootObject());
+                        break;
+                    }
+                    case RPC_SPECIAL_TRANSACT_GET_MAX_THREADS: {
+                        replyStatus = reply.writeInt32(server->getMaxThreads());
+                        break;
+                    }
+                    case RPC_SPECIAL_TRANSACT_GET_CONNECTION_ID: {
+                        // only connections w/ services can be the source of a
+                        // connection ID (so still guarded by non-null server)
+                        //
+                        // connections associated with servers must have an ID
+                        // (hence abort)
+                        int32_t id = connection->getPrivateAccessorForId().get().value();
+                        replyStatus = reply.writeInt32(id);
+                        break;
+                    }
+                    default: {
+                        replyStatus = UNKNOWN_TRANSACTION;
+                    }
                 }
-                default: {
-                    replyStatus = UNKNOWN_TRANSACTION;
-                }
+            } else {
+                ALOGE("Special command sent, but no server object attached.");
             }
         }
     }
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index f4f5151..825fd7c 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -50,7 +50,12 @@
     RpcState();
     ~RpcState();
 
+    // TODO(b/182940634): combine some special transactions into one "getServerInfo" call?
     sp<IBinder> getRootObject(const base::unique_fd& fd, const sp<RpcConnection>& connection);
+    status_t getMaxThreads(const base::unique_fd& fd, const sp<RpcConnection>& connection,
+                           size_t* maxThreadsOut);
+    status_t getConnectionId(const base::unique_fd& fd, const sp<RpcConnection>& connection,
+                             int32_t* connectionIdOut);
 
     [[nodiscard]] status_t transact(const base::unique_fd& fd, const RpcAddress& address,
                                     uint32_t code, const Parcel& data,
diff --git a/libs/binder/RpcWireFormat.h b/libs/binder/RpcWireFormat.h
index 60ec6c9..a7e8a52 100644
--- a/libs/binder/RpcWireFormat.h
+++ b/libs/binder/RpcWireFormat.h
@@ -47,8 +47,12 @@
  */
 enum : uint32_t {
     RPC_SPECIAL_TRANSACT_GET_ROOT = 0,
+    RPC_SPECIAL_TRANSACT_GET_MAX_THREADS = 1,
+    RPC_SPECIAL_TRANSACT_GET_CONNECTION_ID = 2,
 };
 
+constexpr int32_t RPC_CONNECTION_ID_NEW = -1;
+
 // serialization is like:
 // |RpcWireHeader|struct desginated by 'command'| (over and over again)
 
diff --git a/libs/binder/include/private/binder/binder_module.h b/libs/binder/binder_module.h
similarity index 85%
rename from libs/binder/include/private/binder/binder_module.h
rename to libs/binder/binder_module.h
index 151235c..9dea3b4 100644
--- a/libs/binder/include/private/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -29,14 +29,14 @@
 #undef B_PACK_CHARS
 #endif
 
-#include <sys/ioctl.h>
 #include <linux/android/binder.h>
+#include <sys/ioctl.h>
 
 #ifndef BR_FROZEN_REPLY
 // Temporary definition of BR_FROZEN_REPLY. For production
 // this will come from UAPI binder.h
 #define BR_FROZEN_REPLY _IO('r', 18)
-#endif //BR_FROZEN_REPLY
+#endif // BR_FROZEN_REPLY
 
 #ifndef BINDER_FREEZE
 /*
@@ -49,46 +49,46 @@
     //
     // Group-leader PID of process to be frozen
     //
-    uint32_t            pid;
+    uint32_t pid;
     //
     // Enable(1) / Disable(0) freeze for given PID
     //
-    uint32_t            enable;
+    uint32_t enable;
     //
     // Timeout to wait for transactions to drain.
     // 0: don't wait (ioctl will return EAGAIN if not drained)
     // N: number of ms to wait
-    uint32_t            timeout_ms;
+    uint32_t timeout_ms;
 };
-#endif //BINDER_FREEZE
+#endif // BINDER_FREEZE
 
 #ifndef BINDER_GET_FROZEN_INFO
 
-#define BINDER_GET_FROZEN_INFO          _IOWR('b', 15, struct binder_frozen_status_info)
+#define BINDER_GET_FROZEN_INFO _IOWR('b', 15, struct binder_frozen_status_info)
 
 struct binder_frozen_status_info {
     //
     // Group-leader PID of process to be queried
     //
-    __u32            pid;
+    __u32 pid;
     //
     // Indicates whether the process has received any sync calls since last
     // freeze (cleared at freeze/unfreeze)
     //
-    __u32            sync_recv;
+    __u32 sync_recv;
     //
     // Indicates whether the process has received any async calls since last
     // freeze (cleared at freeze/unfreeze)
     //
-    __u32            async_recv;
+    __u32 async_recv;
 };
-#endif //BINDER_GET_FROZEN_INFO
+#endif // BINDER_GET_FROZEN_INFO
 
 #ifndef BR_ONEWAY_SPAM_SUSPECT
 // Temporary definition of BR_ONEWAY_SPAM_SUSPECT. For production
 // this will come from UAPI binder.h
 #define BR_ONEWAY_SPAM_SUSPECT _IO('r', 19)
-#endif //BR_ONEWAY_SPAM_SUSPECT
+#endif // BR_ONEWAY_SPAM_SUSPECT
 
 #ifndef BINDER_ENABLE_ONEWAY_SPAM_DETECTION
 /*
@@ -96,6 +96,6 @@
  * these will be defined in the UAPI binder.h file from upstream kernel.
  */
 #define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
-#endif //BINDER_ENABLE_ONEWAY_SPAM_DETECTION
+#endif // BINDER_ENABLE_ONEWAY_SPAM_DETECTION
 
 #endif // _BINDER_MODULE_H_
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index 52f221d..97c826c 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
diff --git a/libs/binder/include/binder/RpcConnection.h b/libs/binder/include/binder/RpcConnection.h
index 2395e78..87984d7 100644
--- a/libs/binder/include/binder/RpcConnection.h
+++ b/libs/binder/include/binder/RpcConnection.h
@@ -21,7 +21,9 @@
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
+#include <map>
 #include <optional>
+#include <thread>
 #include <vector>
 
 // WARNING: This is a feature which is still in development, and it is subject
@@ -32,6 +34,7 @@
 
 class Parcel;
 class RpcServer;
+class RpcSocketAddress;
 class RpcState;
 
 /**
@@ -43,45 +46,22 @@
     static sp<RpcConnection> make();
 
     /**
-     * This represents a connection for responses, e.g.:
-     *
-     *     process A serves binder a
-     *     process B opens a connection to process A
-     *     process B makes binder b and sends it to A
-     *     A uses this 'back connection' to send things back to B
-     *
-     * This should be called once, and then a call should be made to join per
-     * connection thread.
-     */
-    [[nodiscard]] bool setupUnixDomainServer(const char* path);
-
-    /**
      * This should be called once per thread, matching 'join' in the remote
      * process.
      */
-    [[nodiscard]] bool addUnixDomainClient(const char* path);
+    [[nodiscard]] bool setupUnixDomainClient(const char* path);
 
 #ifdef __BIONIC__
     /**
-     * Creates an RPC server at the current port.
-     */
-    [[nodiscard]] bool setupVsockServer(unsigned int port);
-
-    /**
      * Connects to an RPC server at the CVD & port.
      */
-    [[nodiscard]] bool addVsockClient(unsigned int cvd, unsigned int port);
+    [[nodiscard]] bool setupVsockClient(unsigned int cvd, unsigned int port);
 #endif // __BIONIC__
 
     /**
-     * Creates an RPC server at the current port.
-     */
-    [[nodiscard]] bool setupInetServer(unsigned int port);
-
-    /**
      * Connects to an RPC server at the given address and port.
      */
-    [[nodiscard]] bool addInetClient(const char* addr, unsigned int port);
+    [[nodiscard]] bool setupInetClient(const char* addr, unsigned int port);
 
     /**
      * For debugging!
@@ -98,36 +78,46 @@
      */
     sp<IBinder> getRootObject();
 
+    /**
+     * Query the other side of the connection for the maximum number of threads
+     * it supports (maximum number of concurrent non-nested synchronous transactions)
+     */
+    status_t getMaxThreads(size_t* maxThreads);
+
     [[nodiscard]] status_t transact(const RpcAddress& address, uint32_t code, const Parcel& data,
                                     Parcel* reply, uint32_t flags);
     [[nodiscard]] status_t sendDecStrong(const RpcAddress& address);
 
-    /**
-     * Adds a server thread accepting connections. Must be called after
-     * setup*Server.
-     */
-    void join();
-
     ~RpcConnection();
 
-    void setForServer(const wp<RpcServer>& server);
     wp<RpcServer> server();
 
     // internal only
     const std::unique_ptr<RpcState>& state() { return mState; }
 
-    class SocketAddress {
-    public:
-        virtual ~SocketAddress();
-        virtual std::string toString() const = 0;
-        virtual const sockaddr* addr() const = 0;
-        virtual size_t addrSize() const = 0;
+    class PrivateAccessorForId {
+    private:
+        friend class RpcConnection;
+        friend class RpcState;
+        explicit PrivateAccessorForId(const RpcConnection* connection) : mConnection(connection) {}
+
+        const std::optional<int32_t> get() { return mConnection->mId; }
+
+        const RpcConnection* mConnection;
     };
+    PrivateAccessorForId getPrivateAccessorForId() const { return PrivateAccessorForId(this); }
 
 private:
+    friend PrivateAccessorForId;
     friend sp<RpcConnection>;
+    friend RpcServer;
     RpcConnection();
 
+    status_t readId();
+
+    void startThread(base::unique_fd client);
+    void join(base::unique_fd client);
+
     struct ConnectionSocket : public RefBase {
         base::unique_fd fd;
 
@@ -136,10 +126,11 @@
         std::optional<pid_t> exclusiveTid;
     };
 
-    bool setupSocketServer(const SocketAddress& address);
-    bool addSocketClient(const SocketAddress& address);
-    void addClient(base::unique_fd&& fd);
-    sp<ConnectionSocket> assignServerToThisThread(base::unique_fd&& fd);
+    bool setupSocketClient(const RpcSocketAddress& address);
+    bool setupOneSocketClient(const RpcSocketAddress& address, int32_t connectionId);
+    void addClient(base::unique_fd fd);
+    void setForServer(const wp<RpcServer>& server, int32_t connectionId);
+    sp<ConnectionSocket> assignServerToThisThread(base::unique_fd fd);
     bool removeServerSocket(const sp<ConnectionSocket>& socket);
 
     enum class SocketUse {
@@ -185,16 +176,24 @@
 
     wp<RpcServer> mForServer; // maybe null, for client connections
 
+    // TODO(b/183988761): this shouldn't be guessable
+    std::optional<int32_t> mId;
+
     std::unique_ptr<RpcState> mState;
 
-    base::unique_fd mServer; // socket we are accepting connections on
-
     std::mutex mSocketMutex;           // for all below
+
     std::condition_variable mSocketCv; // for mWaitingThreads
     size_t mWaitingThreads = 0;
     size_t mClientsOffset = 0; // hint index into clients, ++ when sending an async transaction
     std::vector<sp<ConnectionSocket>> mClients;
     std::vector<sp<ConnectionSocket>> mServers;
+
+    // TODO(b/185167543): use for reverse connections (allow client to also
+    // serve calls on a connection).
+    // TODO(b/185167543): allow sharing between different connections in a
+    // process? (or combine with mServers)
+    std::map<std::thread::id, std::thread> mThreads;
 };
 
 } // namespace android
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index a665fad..81ea3a7 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -29,36 +29,83 @@
 
 namespace android {
 
+class RpcSocketAddress;
+
 /**
  * This represents a server of an interface, which may be connected to by any
  * number of clients over sockets.
+ *
+ * Usage:
+ *     auto server = RpcServer::make();
+ *     // only supports one now
+ *     if (!server->setup*Server(...)) {
+ *         :(
+ *     }
+ *     server->join();
  */
 class RpcServer final : public virtual RefBase {
 public:
     static sp<RpcServer> make();
 
+    /**
+     * This represents a connection for responses, e.g.:
+     *
+     *     process A serves binder a
+     *     process B opens a connection to process A
+     *     process B makes binder b and sends it to A
+     *     A uses this 'back connection' to send things back to B
+     */
+    [[nodiscard]] bool setupUnixDomainServer(const char* path);
+
+#ifdef __BIONIC__
+    /**
+     * Creates an RPC server at the current port.
+     */
+    [[nodiscard]] bool setupVsockServer(unsigned int port);
+#endif // __BIONIC__
+
+    /**
+     * Creates an RPC server at the current port using IPv4.
+     *
+     * TODO(b/182914638): IPv6 support
+     *
+     * Set |port| to 0 to pick an ephemeral port; see discussion of
+     * /proc/sys/net/ipv4/ip_local_port_range in ip(7). In this case, |assignedPort|
+     * will be set to the picked port number, if it is not null.
+     */
+    [[nodiscard]] bool setupInetServer(unsigned int port, unsigned int* assignedPort);
+
     void iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
 
     /**
-     * Setup a static connection, when the number of clients are known.
+     * This must be called before adding a client connection.
      *
-     * Each call to this function corresponds to a different client, and clients
-     * each have their own threadpools.
+     * If this is not specified, this will be a single-threaded server.
      *
-     * TODO(b/167966510): support dynamic creation of connections/threads
+     * TODO(b/185167543): these are currently created per client, but these
+     * should be shared.
      */
-    sp<RpcConnection> addClientConnection();
+    void setMaxThreads(size_t threads);
+    size_t getMaxThreads();
 
     /**
      * The root object can be retrieved by any client, without any
      * authentication. TODO(b/183988761)
      */
     void setRootObject(const sp<IBinder>& binder);
+    sp<IBinder> getRootObject();
 
     /**
-     * Root object set with setRootObject
+     * You must have at least one client connection before calling this.
+     *
+     * TODO(b/185167543): way to shut down?
      */
-    sp<IBinder> getRootObject();
+    void join();
+
+    /**
+     * For debugging!
+     */
+    std::vector<sp<RpcConnection>> listConnections();
 
     ~RpcServer();
 
@@ -66,11 +113,17 @@
     friend sp<RpcServer>;
     RpcServer();
 
-    bool mAgreedExperimental = false;
+    bool setupSocketServer(const RpcSocketAddress& address);
 
-    std::mutex mLock;
+    bool mAgreedExperimental = false;
+    bool mStarted = false; // TODO(b/185167543): support dynamically added clients
+    size_t mMaxThreads = 1;
+    base::unique_fd mServer; // socket we are accepting connections on
+
+    std::mutex mLock; // for below
     sp<IBinder> mRootObject;
-    std::vector<sp<RpcConnection>> mConnections; // per-client
+    std::map<int32_t, sp<RpcConnection>> mConnections;
+    int32_t mConnectionIdCounter = 0;
 };
 
 } // namespace android
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 57c9013..49d3401 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -50,6 +50,8 @@
         "//apex_available:platform",
         "com.android.virt",
     ],
+    lints: "none",
+    clippy_lints: "none",
 }
 
 rust_bindgen {
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index 9095af2..1d1a295 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -16,14 +16,6 @@
 
 //! Generated Rust bindings to libbinder_ndk
 
-#![allow(
-    non_camel_case_types,
-    non_snake_case,
-    non_upper_case_globals,
-    unused,
-    improper_ctypes,
-    missing_docs
-)]
 use std::error::Error;
 use std::fmt;
 
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index 2bdb264..814e094 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -18,8 +18,8 @@
     oneway void sendString(@utf8InCpp String str);
     @utf8InCpp String doubleString(@utf8InCpp String str);
 
-    // number of known RPC binders to process, RpcState::countBinders
-    int countBinders();
+    // number of known RPC binders to process, RpcState::countBinders by connection
+    int[] countBinders();
 
     // Caller sends server, callee pings caller's server and returns error code.
     int pingMe(IBinder binder);
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index dc8c0f1..5676bd1 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -31,11 +31,11 @@
 #include <binder/IServiceManager.h>
 #include <binder/ParcelRef.h>
 
-#include <private/binder/binder_module.h>
 #include <linux/sched.h>
 #include <sys/epoll.h>
 #include <sys/prctl.h>
 
+#include "../binder_module.h"
 #include "binderAbiHelper.h"
 
 #define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
diff --git a/libs/binder/tests/binderRpcBenchmark.cpp b/libs/binder/tests/binderRpcBenchmark.cpp
index 7c82226..f64bc5b 100644
--- a/libs/binder/tests/binderRpcBenchmark.cpp
+++ b/libs/binder/tests/binderRpcBenchmark.cpp
@@ -121,21 +121,18 @@
     std::thread([addr]() {
         sp<RpcServer> server = RpcServer::make();
         server->setRootObject(sp<MyBinderRpcBenchmark>::make());
-
         server->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
-
-        sp<RpcConnection> connection = server->addClientConnection();
-        CHECK(connection->setupUnixDomainServer(addr.c_str()));
-
-        connection->join();
+        CHECK(server->setupUnixDomainServer(addr.c_str()));
+        server->join();
     }).detach();
 
     for (size_t tries = 0; tries < 5; tries++) {
         usleep(10000);
-        if (gConnection->addUnixDomainClient(addr.c_str())) goto success;
+        if (gConnection->setupUnixDomainClient(addr.c_str())) goto success;
     }
     LOG(FATAL) << "Could not connect.";
 success:
 
     ::benchmark::RunSpecifiedBenchmarks();
+    return 0;
 }
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index dd68fdb..50bff91 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -17,6 +17,7 @@
 #include <BnBinderRpcSession.h>
 #include <BnBinderRpcTest.h>
 #include <aidl/IBinderRpcTest.h>
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_libbinder.h>
@@ -77,7 +78,7 @@
 
 class MyBinderRpcTest : public BnBinderRpcTest {
 public:
-    sp<RpcConnection> connection;
+    wp<RpcServer> server;
 
     Status sendString(const std::string& str) override {
         (void)str;
@@ -87,13 +88,20 @@
         *strstr = str + str;
         return Status::ok();
     }
-    Status countBinders(int32_t* out) override {
-        if (connection == nullptr) {
+    Status countBinders(std::vector<int32_t>* out) override {
+        sp<RpcServer> spServer = server.promote();
+        if (spServer == nullptr) {
             return Status::fromExceptionCode(Status::EX_NULL_POINTER);
         }
-        *out = connection->state()->countBinders();
-        if (*out != 1) {
-            connection->state()->dump();
+        out->clear();
+        for (auto connection : spServer->listConnections()) {
+            size_t count = connection->state()->countBinders();
+            if (count != 1) {
+                // this is called when there is only one binder held remaining,
+                // so to aid debugging
+                connection->state()->dump();
+            }
+            out->push_back(count);
         }
         return Status::ok();
     }
@@ -176,14 +184,27 @@
 };
 sp<IBinder> MyBinderRpcTest::mHeldBinder;
 
+class Pipe {
+public:
+    Pipe() { CHECK(android::base::Pipe(&mRead, &mWrite)); }
+    Pipe(Pipe&&) = default;
+    android::base::borrowed_fd readEnd() { return mRead; }
+    android::base::borrowed_fd writeEnd() { return mWrite; }
+
+private:
+    android::base::unique_fd mRead;
+    android::base::unique_fd mWrite;
+};
+
 class Process {
 public:
-    Process(const std::function<void()>& f) {
+    Process(Process&&) = default;
+    Process(const std::function<void(Pipe*)>& f) {
         if (0 == (mPid = fork())) {
             // racey: assume parent doesn't crash before this is set
             prctl(PR_SET_PDEATHSIG, SIGHUP);
 
-            f();
+            f(&mPipe);
         }
     }
     ~Process() {
@@ -191,9 +212,11 @@
             kill(mPid, SIGKILL);
         }
     }
+    Pipe* getPipe() { return &mPipe; }
 
 private:
     pid_t mPid = 0;
+    Pipe mPipe;
 };
 
 static std::string allocateSocketAddress() {
@@ -206,24 +229,33 @@
     // reference to process hosting a socket server
     Process host;
 
-    // client connection object associated with other process
-    sp<RpcConnection> connection;
+    struct ConnectionInfo {
+        sp<RpcConnection> connection;
+        sp<IBinder> root;
+    };
 
-    // pre-fetched root object
-    sp<IBinder> rootBinder;
+    // client connection objects associated with other process
+    // each one represents a separate connection
+    std::vector<ConnectionInfo> connections;
 
-    // whether connection should be invalidated by end of run
-    bool expectInvalid = false;
-
+    ProcessConnection(ProcessConnection&&) = default;
     ~ProcessConnection() {
-        rootBinder = nullptr;
-        EXPECT_NE(nullptr, connection);
-        EXPECT_NE(nullptr, connection->state());
-        EXPECT_EQ(0, connection->state()->countBinders()) << (connection->state()->dump(), "dump:");
+        for (auto& connection : connections) {
+            connection.root = nullptr;
+        }
 
-        wp<RpcConnection> weakConnection = connection;
-        connection = nullptr;
-        EXPECT_EQ(nullptr, weakConnection.promote()) << "Leaked connection";
+        for (auto& info : connections) {
+            sp<RpcConnection>& connection = info.connection;
+
+            EXPECT_NE(nullptr, connection);
+            EXPECT_NE(nullptr, connection->state());
+            EXPECT_EQ(0, connection->state()->countBinders())
+                    << (connection->state()->dump(), "dump:");
+
+            wp<RpcConnection> weakConnection = connection;
+            connection = nullptr;
+            EXPECT_EQ(nullptr, weakConnection.promote()) << "Leaked connection";
+        }
     }
 };
 
@@ -232,18 +264,25 @@
 struct BinderRpcTestProcessConnection {
     ProcessConnection proc;
 
-    // pre-fetched root object
+    // pre-fetched root object (for first connection)
     sp<IBinder> rootBinder;
 
-    // pre-casted root object
+    // pre-casted root object (for first connection)
     sp<IBinderRpcTest> rootIface;
 
+    // whether connection should be invalidated by end of run
+    bool expectInvalid = false;
+
+    BinderRpcTestProcessConnection(BinderRpcTestProcessConnection&&) = default;
     ~BinderRpcTestProcessConnection() {
-        if (!proc.expectInvalid) {
-            int32_t remoteBinders = 0;
-            EXPECT_OK(rootIface->countBinders(&remoteBinders));
-            // should only be the root binder object, iface
-            EXPECT_EQ(remoteBinders, 1);
+        if (!expectInvalid) {
+            std::vector<int32_t> remoteCounts;
+            // calling over any connections counts across all connections
+            EXPECT_OK(rootIface->countBinders(&remoteCounts));
+            EXPECT_EQ(remoteCounts.size(), proc.connections.size());
+            for (auto remoteCount : remoteCounts) {
+                EXPECT_EQ(remoteCount, 1);
+            }
         }
 
         rootIface = nullptr;
@@ -278,97 +317,98 @@
     // This creates a new process serving an interface on a certain number of
     // threads.
     ProcessConnection createRpcTestSocketServerProcess(
-            size_t numThreads,
-            const std::function<void(const sp<RpcServer>&, const sp<RpcConnection>&)>& configure) {
-        CHECK_GT(numThreads, 0);
+            size_t numThreads, size_t numConnections,
+            const std::function<void(const sp<RpcServer>&)>& configure) {
+        CHECK_GE(numConnections, 1) << "Must have at least one connection to a server";
 
         SocketType socketType = GetParam();
 
         std::string addr = allocateSocketAddress();
         unlink(addr.c_str());
-        static unsigned int port = 3456;
-        port++;
+        static unsigned int vsockPort = 3456;
+        vsockPort++;
 
         auto ret = ProcessConnection{
-                .host = Process([&] {
+                .host = Process([&](Pipe* pipe) {
                     sp<RpcServer> server = RpcServer::make();
 
                     server->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
-
-                    // server supporting one client on one socket
-                    sp<RpcConnection> connection = server->addClientConnection();
+                    server->setMaxThreads(numThreads);
 
                     switch (socketType) {
                         case SocketType::UNIX:
-                            CHECK(connection->setupUnixDomainServer(addr.c_str())) << addr;
+                            CHECK(server->setupUnixDomainServer(addr.c_str())) << addr;
                             break;
 #ifdef __BIONIC__
                         case SocketType::VSOCK:
-                            CHECK(connection->setupVsockServer(port));
+                            CHECK(server->setupVsockServer(vsockPort));
                             break;
 #endif // __BIONIC__
-                        case SocketType::INET:
-                            CHECK(connection->setupInetServer(port));
+                        case SocketType::INET: {
+                            unsigned int outPort = 0;
+                            CHECK(server->setupInetServer(0, &outPort));
+                            CHECK_NE(0, outPort);
+                            CHECK(android::base::WriteFully(pipe->writeEnd(), &outPort,
+                                                            sizeof(outPort)));
                             break;
+                        }
                         default:
                             LOG_ALWAYS_FATAL("Unknown socket type");
                     }
 
-                    configure(server, connection);
+                    configure(server);
 
-                    // accept 'numThreads' connections
-                    std::vector<std::thread> pool;
-                    for (size_t i = 0; i + 1 < numThreads; i++) {
-                        pool.push_back(std::thread([=] { connection->join(); }));
-                    }
-                    connection->join();
-                    for (auto& t : pool) t.join();
+                    server->join();
                 }),
-                .connection = RpcConnection::make(),
         };
 
-        // create remainder of connections
-        for (size_t i = 0; i < numThreads; i++) {
-            for (size_t tries = 0; tries < 5; tries++) {
+        unsigned int inetPort = 0;
+        if (socketType == SocketType::INET) {
+            CHECK(android::base::ReadFully(ret.host.getPipe()->readEnd(), &inetPort,
+                                           sizeof(inetPort)));
+            CHECK_NE(0, inetPort);
+        }
+
+        for (size_t i = 0; i < numConnections; i++) {
+            sp<RpcConnection> connection = RpcConnection::make();
+            for (size_t tries = 0; tries < 10; tries++) {
                 usleep(10000);
                 switch (socketType) {
                     case SocketType::UNIX:
-                        if (ret.connection->addUnixDomainClient(addr.c_str())) goto success;
+                        if (connection->setupUnixDomainClient(addr.c_str())) goto success;
                         break;
 #ifdef __BIONIC__
                     case SocketType::VSOCK:
-                        if (ret.connection->addVsockClient(VMADDR_CID_LOCAL, port)) goto success;
+                        if (connection->setupVsockClient(VMADDR_CID_LOCAL, vsockPort)) goto success;
                         break;
 #endif // __BIONIC__
                     case SocketType::INET:
-                        if (ret.connection->addInetClient("127.0.0.1", port)) goto success;
+                        if (connection->setupInetClient("127.0.0.1", inetPort)) goto success;
                         break;
                     default:
                         LOG_ALWAYS_FATAL("Unknown socket type");
                 }
             }
             LOG_ALWAYS_FATAL("Could not connect");
-        success:;
+        success:
+            ret.connections.push_back({connection, connection->getRootObject()});
         }
-
-        ret.rootBinder = ret.connection->getRootObject();
         return ret;
     }
 
-    BinderRpcTestProcessConnection createRpcTestSocketServerProcess(size_t numThreads) {
+    BinderRpcTestProcessConnection createRpcTestSocketServerProcess(size_t numThreads,
+                                                                    size_t numConnections = 1) {
         BinderRpcTestProcessConnection ret{
-                .proc = createRpcTestSocketServerProcess(numThreads,
-                                                         [&](const sp<RpcServer>& server,
-                                                             const sp<RpcConnection>& connection) {
+                .proc = createRpcTestSocketServerProcess(numThreads, numConnections,
+                                                         [&](const sp<RpcServer>& server) {
                                                              sp<MyBinderRpcTest> service =
                                                                      new MyBinderRpcTest;
                                                              server->setRootObject(service);
-                                                             service->connection =
-                                                                     connection; // for testing only
+                                                             service->server = server;
                                                          }),
         };
 
-        ret.rootBinder = ret.proc.rootBinder;
+        ret.rootBinder = ret.proc.connections.at(0).root;
         ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
 
         return ret;
@@ -376,18 +416,12 @@
 };
 
 TEST_P(BinderRpc, RootObjectIsNull) {
-    auto proc = createRpcTestSocketServerProcess(1,
-                                                 [](const sp<RpcServer>& server,
-                                                    const sp<RpcConnection>&) {
-                                                     // this is the default, but to be explicit
-                                                     server->setRootObject(nullptr);
-                                                 });
+    auto proc = createRpcTestSocketServerProcess(1, 1, [](const sp<RpcServer>& server) {
+        // this is the default, but to be explicit
+        server->setRootObject(nullptr);
+    });
 
-    // retrieved by getRootObject when process is created above
-    EXPECT_EQ(nullptr, proc.rootBinder);
-
-    // make sure we can retrieve it again (process doesn't crash)
-    EXPECT_EQ(nullptr, proc.connection->getRootObject());
+    EXPECT_EQ(nullptr, proc.connections.at(0).root);
 }
 
 TEST_P(BinderRpc, Ping) {
@@ -402,6 +436,14 @@
     EXPECT_EQ(IBinderRpcTest::descriptor, proc.rootBinder->getInterfaceDescriptor());
 }
 
+TEST_P(BinderRpc, MultipleConnections) {
+    auto proc = createRpcTestSocketServerProcess(1 /*threads*/, 5 /*connections*/);
+    for (auto connection : proc.proc.connections) {
+        ASSERT_NE(nullptr, connection.root);
+        EXPECT_EQ(OK, connection.root->pingBinder());
+    }
+}
+
 TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
     auto proc = createRpcTestSocketServerProcess(1);
     Parcel data;
@@ -549,6 +591,15 @@
               proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
 }
 
+TEST_P(BinderRpc, CannotMixBindersBetweenTwoConnectionsToTheSameServer) {
+    auto proc = createRpcTestSocketServerProcess(1 /*threads*/, 2 /*connections*/);
+
+    sp<IBinder> outBinder;
+    EXPECT_EQ(INVALID_OPERATION,
+              proc.rootIface->repeatBinder(proc.proc.connections.at(1).root, &outBinder)
+                      .transactionError());
+}
+
 TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
@@ -833,7 +884,7 @@
         EXPECT_EQ(DEAD_OBJECT, proc.rootIface->die(doDeathCleanup).transactionError())
                 << "Do death cleanup: " << doDeathCleanup;
 
-        proc.proc.expectInvalid = true;
+        proc.expectInvalid = true;
     }
 }
 
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 8a82c2a..29c788b 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -47,6 +47,7 @@
 
 // Native processes to dump on debuggable builds.
 static const char* debuggable_native_processes_to_dump[] = {
+        "/system/bin/keystore2",
         "/system/bin/vold",
         NULL,
 };
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 3d854c2..37fb844 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -666,12 +666,12 @@
     void run() {
         std::unique_lock<std::mutex> lock(mMutex);
         while (!mDone) {
-            mCv.wait(lock);
             while (!mRunnables.empty()) {
                 std::function<void()> runnable = mRunnables.front();
                 mRunnables.pop_front();
                 runnable();
             }
+            mCv.wait(lock);
         }
     }
 
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
index e707684..0827bbe 100644
--- a/libs/gui/LayerDebugInfo.cpp
+++ b/libs/gui/LayerDebugInfo.cpp
@@ -119,9 +119,11 @@
     info.mSurfaceDamageRegion.dump(result, "SurfaceDamageRegion");
     if (info.mStretchEffect.hasEffect()) {
         const auto& se = info.mStretchEffect;
-        StringAppendF(&result, "  StretchEffect area=[%f, %f, %f, %f] vec=(%f, %f) maxAmount=%f\n",
-                      se.area.left, se.area.top, se.area.right, se.area.bottom, se.vectorX,
-                      se.vectorY, se.maxAmount);
+        StringAppendF(&result,
+                      "  StretchEffect width = %f, height = %f vec=(%f, %f) "
+                      "maxAmount=(%f, %f)\n",
+                      se.width, se.height,
+                      se.vectorX, se.vectorY, se.maxAmountX, se.maxAmountY);
     }
 
     StringAppendF(&result, "      layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ",
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 517b49e..267db76 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -98,7 +98,6 @@
     SAFE_PARCEL(output.write, transparentRegion);
     SAFE_PARCEL(output.writeUint32, transform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
-    SAFE_PARCEL(output.write, crop);
     SAFE_PARCEL(output.write, orientedDisplaySpaceRect);
 
     if (buffer) {
@@ -167,6 +166,7 @@
     }
 
     SAFE_PARCEL(output.write, stretchEffect);
+    SAFE_PARCEL(output.write, bufferCrop);
 
     return NO_ERROR;
 }
@@ -209,7 +209,6 @@
     SAFE_PARCEL(input.read, transparentRegion);
     SAFE_PARCEL(input.readUint32, &transform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
-    SAFE_PARCEL(input.read, crop);
     SAFE_PARCEL(input.read, orientedDisplaySpaceRect);
 
     bool tmpBool = false;
@@ -296,6 +295,7 @@
     }
 
     SAFE_PARCEL(input.read, stretchEffect);
+    SAFE_PARCEL(input.read, bufferCrop);
 
     return NO_ERROR;
 }
@@ -539,6 +539,10 @@
         what |= eStretchChanged;
         stretchEffect = other.stretchEffect;
     }
+    if (other.what & eBufferCropChanged) {
+        what |= eBufferCropChanged;
+        bufferCrop = other.bufferCrop;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIu64 " what=0x%" PRIu64,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 5db0eae..11b8eba 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1648,8 +1648,7 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setStretchEffect(
-        const sp<SurfaceControl>& sc, float left, float top, float right, float bottom, float vecX,
-        float vecY, float maxAmount) {
+    const sp<SurfaceControl>& sc, const StretchEffect& stretchEffect) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1657,10 +1656,22 @@
     }
 
     s->what |= layer_state_t::eStretchChanged;
-    s->stretchEffect = StretchEffect{.area = {left, top, right, bottom},
-                                     .vectorX = vecX,
-                                     .vectorY = vecY,
-                                     .maxAmount = maxAmount};
+    s->stretchEffect = stretchEffect;
+    return *this;
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferCrop(
+        const sp<SurfaceControl>& sc, const Rect& bufferCrop) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    s->what |= layer_state_t::eBufferCropChanged;
+    s->bufferCrop = bufferCrop;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index b4f62f2..3947f22 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -85,7 +85,7 @@
         eReleaseBufferListenerChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
         eLayerCreated = 0x00001000,
-        /* was eDetachChildren, now available 0x00002000, */
+        eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
         eColorChanged = 0x00010000,
@@ -227,6 +227,8 @@
     // Stretch effect to be applied to this layer
     StretchEffect stretchEffect;
 
+    Rect bufferCrop;
+
     // Listens to when the buffer is safe to be released. This is used for blast
     // layers only. The callback includes a release fence as well as the graphic
     // buffer id to identify the buffer.
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 5bbd8e3..35757be 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -538,9 +538,22 @@
         // transactions from blocking each other.
         Transaction& setApplyToken(const sp<IBinder>& token);
 
-        Transaction& setStretchEffect(const sp<SurfaceControl>& sc, float left, float top,
-                                      float right, float bottom, float vecX, float vecY,
-                                      float maxAmount);
+        /**
+         * Provides the stretch effect configured on a container that the
+         * surface is rendered within.
+         * @param sc target surface the stretch should be applied to
+         * @param stretchEffect the corresponding stretch effect to be applied
+         *    to the surface. This can be directly on the surface itself or
+         *    configured from a parent of the surface in which case the
+         *    StretchEffect provided has parameters mapping the position of
+         *    the surface within the container that has the stretch configured
+         *    on it
+         * @return The transaction being constructed
+         */
+        Transaction& setStretchEffect(const sp<SurfaceControl>& sc,
+                                      const StretchEffect& stretchEffect);
+
+        Transaction& setBufferCrop(const sp<SurfaceControl>& sc, const Rect& bufferCrop);
 
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 5600eb3..deb4679 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -340,7 +340,8 @@
                              int32_t flags, int32_t edgeFlags, int32_t metaState,
                              int32_t buttonState, MotionClassification classification,
                              const ui::Transform& transform, float xPrecision, float yPrecision,
-                             float rawXCursorPosition, float rawYCursorPosition, nsecs_t downTime,
+                             float rawXCursorPosition, float rawYCursorPosition,
+                             int32_t displayWidth, int32_t displayHeight, nsecs_t downTime,
                              nsecs_t eventTime, size_t pointerCount,
                              const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
@@ -357,6 +358,8 @@
     mYPrecision = yPrecision;
     mRawXCursorPosition = rawXCursorPosition;
     mRawYCursorPosition = rawYCursorPosition;
+    mDisplayWidth = displayWidth;
+    mDisplayHeight = displayHeight;
     mDownTime = downTime;
     mPointerProperties.clear();
     mPointerProperties.appendArray(pointerProperties, pointerCount);
@@ -380,6 +383,8 @@
     mYPrecision = other->mYPrecision;
     mRawXCursorPosition = other->mRawXCursorPosition;
     mRawYCursorPosition = other->mRawYCursorPosition;
+    mDisplayWidth = other->mDisplayWidth;
+    mDisplayHeight = other->mDisplayHeight;
     mDownTime = other->mDownTime;
     mPointerProperties = other->mPointerProperties;
 
@@ -426,7 +431,7 @@
 }
 
 float MotionEvent::getRawAxisValue(int32_t axis, size_t pointerIndex) const {
-    return getRawPointerCoords(pointerIndex)->getAxisValue(axis);
+    return getHistoricalRawAxisValue(axis, pointerIndex, getHistorySize());
 }
 
 float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const {
@@ -440,7 +445,32 @@
 
 float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
         size_t historicalIndex) const {
-    return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+    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);
+    }
+
+    // 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];
 }
 
 float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
@@ -449,19 +479,10 @@
         return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
     }
 
-    float rawX = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getX();
-    float rawY = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getY();
-    vec2 vals = mTransform.transform(rawX, rawY);
-
-    switch (axis) {
-    case AMOTION_EVENT_AXIS_X:
-        return vals.x;
-    case AMOTION_EVENT_AXIS_Y:
-        return vals.y;
-    }
-
-    // This should never happen
-    return 0;
+    vec2 vals = mTransform.transform(
+            getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getXYValue());
+    static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
+    return vals[axis];
 }
 
 ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
@@ -606,6 +627,8 @@
     mYPrecision = parcel->readFloat();
     mRawXCursorPosition = parcel->readFloat();
     mRawYCursorPosition = parcel->readFloat();
+    mDisplayWidth = parcel->readInt32();
+    mDisplayHeight = parcel->readInt32();
     mDownTime = parcel->readInt64();
 
     mPointerProperties.clear();
@@ -665,6 +688,8 @@
     parcel->writeFloat(mYPrecision);
     parcel->writeFloat(mRawXCursorPosition);
     parcel->writeFloat(mRawYCursorPosition);
+    parcel->writeInt32(mDisplayWidth);
+    parcel->writeInt32(mDisplayHeight);
     parcel->writeInt64(mDownTime);
 
     for (size_t i = 0; i < pointerCount; i++) {
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 31027b6..61d72ad 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -87,8 +87,10 @@
     // Search system repository.
     std::string path;
 
-    // Treblized input device config files will be located /odm/usr or /vendor/usr.
-    const char *rootsForPartition[] {"/odm", "/vendor", getenv("ANDROID_ROOT")};
+    // Treblized input device config files will be located /product/usr, /system_ext/usr,
+    // /odm/usr or /vendor/usr.
+    const char* rootsForPartition[]{"/product", "/system_ext", "/odm", "/vendor",
+                                    getenv("ANDROID_ROOT")};
     for (size_t i = 0; i < size(rootsForPartition); i++) {
         if (rootsForPartition[i] == nullptr) {
             continue;
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index c68cd9e..dd1c462 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -242,6 +242,10 @@
             msg->body.motion.xCursorPosition = body.motion.xCursorPosition;
             // float yCursorPosition
             msg->body.motion.yCursorPosition = body.motion.yCursorPosition;
+            // int32_t displayW
+            msg->body.motion.displayWidth = body.motion.displayWidth;
+            // int32_t displayH
+            msg->body.motion.displayHeight = body.motion.displayHeight;
             // uint32_t pointerCount
             msg->body.motion.pointerCount = body.motion.pointerCount;
             //struct Pointer pointers[MAX_POINTERS]
@@ -529,9 +533,9 @@
         std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
         int32_t edgeFlags, int32_t metaState, int32_t buttonState,
         MotionClassification classification, const ui::Transform& transform, float xPrecision,
-        float yPrecision, float xCursorPosition, float yCursorPosition, nsecs_t downTime,
-        nsecs_t eventTime, uint32_t pointerCount, const PointerProperties* pointerProperties,
-        const PointerCoords* pointerCoords) {
+        float yPrecision, float xCursorPosition, float yCursorPosition, int32_t displayWidth,
+        int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
+        const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
     if (ATRACE_ENABLED()) {
         std::string message = StringPrintf(
                 "publishMotionEvent(inputChannel=%s, action=%" PRId32 ")",
@@ -589,6 +593,8 @@
     msg.body.motion.yPrecision = yPrecision;
     msg.body.motion.xCursorPosition = xCursorPosition;
     msg.body.motion.yCursorPosition = yCursorPosition;
+    msg.body.motion.displayWidth = displayWidth;
+    msg.body.motion.displayHeight = displayHeight;
     msg.body.motion.downTime = downTime;
     msg.body.motion.eventTime = eventTime;
     msg.body.motion.pointerCount = pointerCount;
@@ -1355,6 +1361,7 @@
                       msg->body.motion.buttonState, msg->body.motion.classification, transform,
                       msg->body.motion.xPrecision, msg->body.motion.yPrecision,
                       msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
+                      msg->body.motion.displayWidth, msg->body.motion.displayHeight,
                       msg->body.motion.downTime, msg->body.motion.eventTime, pointerCount,
                       pointerProperties, pointerCoords);
 }
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 601d8da..15ab428 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -271,6 +271,7 @@
                       AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
                       MotionClassification::NONE, mTransform, 2.0f, 2.1f,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                      AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                       ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, pointerProperties,
                       pointerCoords);
 
@@ -592,6 +593,7 @@
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                      0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                      0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
                      pointerCoords);
     float originalRawX = 0 + 3;
@@ -634,6 +636,71 @@
     ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001);
 }
 
+TEST_F(MotionEventTest, RawCompatTransform) {
+    auto createTouchDownEvent = [](int x, int y, ui::Transform transform) {
+        std::vector<PointerProperties> pointerProperties;
+        pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
+        std::vector<PointerCoords> pointerCoords;
+        pointerCoords.emplace_back().clear();
+        pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        MotionEvent event;
+        event.initialize(InputEvent::nextId(), /* deviceId */ 1, AINPUT_SOURCE_TOUCHSCREEN,
+                         /* displayId */ 0, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+                         /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
+                         /* buttonState */ 0, MotionClassification::NONE, transform,
+                         /* xPrecision */ 0, /* yPrecision */ 0,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, /* displayWidth */ 400,
+                         /* displayHeight */ 800, eventTime, eventTime, pointerCoords.size(),
+                         pointerProperties.data(), pointerCoords.data());
+        return event;
+    };
+
+    {
+        // Make sure raw is raw regardless of transform translation.
+        ui::Transform xform;
+        xform.set(20, 40);
+        MotionEvent event = createTouchDownEvent(60, 100, xform);
+        ASSERT_EQ(60, event.getRawX(0));
+        ASSERT_EQ(100, event.getRawY(0));
+        ASSERT_NE(event.getRawX(0), event.getX(0));
+        ASSERT_NE(event.getRawY(0), event.getY(0));
+    }
+
+    // Next check that getRaw contains rotation (for compatibility) but otherwise is still
+    // "Screen-space". The following tests check all 3 rotations.
+    {
+        // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
+        ui::Transform xform(ui::Transform::ROT_90, 800, 400);
+        xform.set(xform.tx() + 20, xform.ty() + 40);
+        MotionEvent event = createTouchDownEvent(60, 100, xform);
+        ASSERT_EQ(700, event.getRawX(0));
+        ASSERT_EQ(60, event.getRawY(0));
+        ASSERT_NE(event.getRawX(0), event.getX(0));
+        ASSERT_NE(event.getRawY(0), event.getY(0));
+    }
+
+    {
+        // Same as above, but check rotate-180.
+        ui::Transform xform(ui::Transform::ROT_180, 400, 800);
+        xform.set(xform.tx() + 20, xform.ty() + 40);
+        MotionEvent event = createTouchDownEvent(60, 100, xform);
+        ASSERT_EQ(340, event.getRawX(0));
+        ASSERT_EQ(700, event.getRawY(0));
+    }
+
+    {
+        // Same as above, but check rotate-270.
+        ui::Transform xform(ui::Transform::ROT_270, 800, 400);
+        xform.set(xform.tx() + 20, xform.ty() + 40);
+        MotionEvent event = createTouchDownEvent(60, 100, xform);
+        ASSERT_EQ(100, event.getRawX(0));
+        ASSERT_EQ(340, event.getRawY(0));
+    }
+}
+
 TEST_F(MotionEventTest, Initialize_SetsClassification) {
     std::array<MotionClassification, 3> classifications = {
             MotionClassification::NONE,
@@ -657,7 +724,8 @@
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, classification,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, 0 /*downTime*/, 0 /*eventTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                         AMOTION_EVENT_INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/,
                          pointerCount, pointerProperties, pointerCoords);
         ASSERT_EQ(classification, event.getClassification());
     }
@@ -678,8 +746,10 @@
     event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, DISPLAY_ID,
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE,
                      AMETA_NONE, 0, MotionClassification::NONE, identityTransform, 0, 0,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, 0 /*downTime*/,
-                     0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
+                     pointerCoords);
     event.offsetLocation(20, 60);
     ASSERT_EQ(280, event.getRawXCursorPosition());
     ASSERT_EQ(540, event.getRawYCursorPosition());
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 088e00b..a2cfaa1 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -162,6 +162,8 @@
     constexpr float yPrecision = 0.5;
     constexpr float xCursorPosition = 1.3;
     constexpr float yCursorPosition = 50.6;
+    constexpr int32_t displayWidth = 1000;
+    constexpr int32_t displayHeight = 2000;
     constexpr nsecs_t downTime = 3;
     constexpr size_t pointerCount = 3;
     constexpr nsecs_t eventTime = 4;
@@ -190,8 +192,9 @@
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, downTime, eventTime,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            xCursorPosition, yCursorPosition, displayWidth,
+                                            displayHeight, downTime, eventTime, pointerCount,
+                                            pointerProperties, pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
@@ -228,6 +231,8 @@
     EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition());
     EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition());
     EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition());
+    EXPECT_EQ(displayWidth, motionEvent->getDisplaySize().x);
+    EXPECT_EQ(displayHeight, motionEvent->getDisplaySize().y);
     EXPECT_EQ(downTime, motionEvent->getDownTime());
     EXPECT_EQ(eventTime, motionEvent->getEventTime());
     EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
@@ -455,7 +460,7 @@
     status = mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0,
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
                                             pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -471,7 +476,7 @@
     status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0,
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
                                             pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
@@ -492,7 +497,7 @@
     status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0,
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
                                             pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 585779e..5861d55 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -74,9 +74,11 @@
   CHECK_OFFSET(InputMessage::Body::Motion, yPrecision, 124);
   CHECK_OFFSET(InputMessage::Body::Motion, xCursorPosition, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, yCursorPosition, 132);
-  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 136);
-  CHECK_OFFSET(InputMessage::Body::Motion, empty3, 140);
-  CHECK_OFFSET(InputMessage::Body::Motion, pointers, 144);
+  CHECK_OFFSET(InputMessage::Body::Motion, displayWidth, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, displayHeight, 140);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 144);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty3, 148);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointers, 152);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index d049d05..aefc2ec 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -183,7 +183,8 @@
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                          MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                          0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, 0 /*downTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                         AMOTION_EVENT_INVALID_DISPLAY_SIZE, 0 /*downTime*/,
                          entry.eventTime.count(), pointerCount, properties, coords);
 
         events.emplace_back(event);
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index 36f87b8..f79098c 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -46,8 +46,10 @@
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags,
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, 100 /*downTime*/,
-                     200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     100 /*downTime*/, 200 /*eventTime*/, pointerCount, pointerProperties,
+                     pointerCoords);
     return event;
 }
 
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 3865ba5..f0e1c4d 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -300,6 +300,9 @@
  * this ANativeWindow is consumed by something other than the system compositor,
  * e.g. a media codec, this call has no effect.
  *
+ * You can register for changes in the refresh rate using
+ * \a AChoreographer_registerRefreshRateCallback.
+ *
  * Available since API level 31.
  *
  * \param window pointer to an ANativeWindow object.
diff --git a/libs/permission/Android.bp b/libs/permission/Android.bp
index a5712b3..3243a6b 100644
--- a/libs/permission/Android.bp
+++ b/libs/permission/Android.bp
@@ -7,17 +7,43 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aidl_interface {
+    name: "framework-permission-aidl",
+    unstable: true,
+    local_include_dir: "aidl",
+    backend: {
+        ndk: {
+            enabled: false
+        }
+    },
+    srcs: [
+        "aidl/android/content/AttributionSourceState.aidl",
+        "aidl/android/permission/IPermissionChecker.aidl",
+    ],
+}
+
 cc_library_shared {
     name: "libpermission",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
     srcs: [
         "AppOpsManager.cpp",
         "IAppOpsCallback.cpp",
         "IAppOpsService.cpp",
+        "android/permission/PermissionChecker.cpp",
     ],
     export_include_dirs: ["include"],
     shared_libs: [
-        "libbinder",
-        "liblog",
         "libutils",
+        "libbinder",
+        "libcutils",
+        "liblog",
     ],
+    static_libs: [
+        "framework-permission-aidl-cpp",
+    ],
+    export_static_lib_headers: ["framework-permission-aidl-cpp"],
 }
diff --git a/libs/permission/aidl/android/content/AttributionSourceState.aidl b/libs/permission/aidl/android/content/AttributionSourceState.aidl
new file mode 100644
index 0000000..b6e54bf
--- /dev/null
+++ b/libs/permission/aidl/android/content/AttributionSourceState.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 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.content;
+
+/**
+ * Payload for the {@link AttributionSource} class needed to interoperate
+ * with different languages.
+ *
+ * {@hide}
+ */
+parcelable AttributionSourceState {
+    /** The UID that is accessing the permission protected data. */
+    int uid;
+    /** The package that is accessing the permission protected data. */
+    @nullable @utf8InCpp String packageName;
+    /** The attribution tag of the app accessing the permission protected data. */
+    @nullable @utf8InCpp String attributionTag;
+    /** Unique token for that source. */
+    @nullable IBinder token;
+    /** Permissions that should be considered revoked regardless if granted. */
+    @nullable @utf8InCpp String[] renouncedPermissions;
+    /** The next app to receive the permission protected data. */
+    // TODO: We use an array as a workaround - the C++ backend doesn't
+    // support referring to the parcelable as it expects ctor/dtor
+    @nullable AttributionSourceState[] next;
+}
diff --git a/libs/permission/aidl/android/permission/IPermissionChecker.aidl b/libs/permission/aidl/android/permission/IPermissionChecker.aidl
new file mode 100644
index 0000000..1f0e32d
--- /dev/null
+++ b/libs/permission/aidl/android/permission/IPermissionChecker.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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.permission;
+
+import android.content.AttributionSourceState;
+
+/**
+ * Interface to communicate directly with the permission checker service.
+ */
+interface IPermissionChecker {
+    const int PERMISSION_GRANTED = 0;
+    const int PERMISSION_SOFT_DENIED = 1;
+    const int PERMISSION_HARD_DENIED = 2;
+
+    int checkPermission(String permission, in AttributionSourceState attributionSource,
+            @nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+            boolean fromDatasource);
+
+    void finishDataDelivery(String op, in AttributionSourceState attributionSource);
+
+    int checkOp(int op, in AttributionSourceState attributionSource,
+            String message, boolean forDataDelivery, boolean startDataDelivery);
+}
diff --git a/libs/permission/android/permission/PermissionChecker.cpp b/libs/permission/android/permission/PermissionChecker.cpp
new file mode 100644
index 0000000..a8083ee
--- /dev/null
+++ b/libs/permission/android/permission/PermissionChecker.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <mutex>
+#include <include/android/permission/PermissionChecker.h>
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+
+#include <utils/SystemClock.h>
+
+#include <sys/types.h>
+#include <private/android_filesystem_config.h>
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "PermissionChecker"
+
+namespace android {
+
+using android::content::AttributionSourceState;
+
+PermissionChecker::PermissionChecker()
+{
+}
+
+sp<IPermissionChecker> PermissionChecker::getService()
+{
+    static String16 permission_checker("permission_checker");
+
+    std::lock_guard<Mutex> scoped_lock(mLock);
+    int64_t startTime = 0;
+    sp<IPermissionChecker> service = mService;
+    while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+        sp<IBinder> binder = defaultServiceManager()->checkService(permission_checker);
+        if (binder == nullptr) {
+            // Wait for the permission checker service to come back...
+            if (startTime == 0) {
+                startTime = uptimeMillis();
+                ALOGW("Waiting for permission checker service");
+            } else if ((uptimeMillis() - startTime) > 10000) {
+                ALOGE("Waiting too long for permission checker service, giving up");
+                service = nullptr;
+                break;
+            }
+            sleep(1);
+        } else {
+            mService = interface_cast<IPermissionChecker>(binder);
+        }
+    }
+    return mService;
+}
+
+PermissionChecker::PermissionResult
+    PermissionChecker::checkPermissionForDataDeliveryFromDatasource(
+        const String16& permission, AttributionSourceState& attributionSource,
+        const String16& message)
+{
+    return static_cast<PermissionResult>(checkPermission(permission, attributionSource, message,
+            /*forDataDelivery*/ true, /*startDataDelivery*/ false,/*fromDatasource*/ true));
+}
+
+PermissionChecker::PermissionResult
+    PermissionChecker::checkPermissionForStartDataDeliveryFromDatasource(
+        const String16& permission, AttributionSourceState& attributionSource,
+        const String16& message)
+{
+    return static_cast<PermissionResult>(checkPermission(permission, attributionSource, message,
+            /*forDataDelivery*/ true, /*startDataDelivery*/ true, /*fromDatasource*/ true));
+}
+
+void PermissionChecker::finishDataDelivery(const String16& op,
+        AttributionSourceState& attributionSource)
+{
+    sp<IPermissionChecker> service = getService();
+    if (service != nullptr) {
+        binder::Status status = service->finishDataDelivery(op, attributionSource);
+        if (!status.isOk()) {
+            ALOGE("finishDataDelivery failed: %s", status.exceptionMessage().c_str());
+        }
+    }
+}
+
+int32_t PermissionChecker::checkPermission(const String16& permission,
+        AttributionSourceState& attributionSource, const String16& message,
+        bool forDataDelivery, bool startDataDelivery, bool fromDatasource)
+{
+    sp<IPermissionChecker> service = getService();
+    if (service != nullptr) {
+        int32_t result;
+        binder::Status status = service->checkPermission(permission, attributionSource, message,
+                forDataDelivery, startDataDelivery, fromDatasource, &result);
+        if (status.isOk()) {
+            return result;
+        }
+        ALOGE("checkPermission failed: %s", status.exceptionMessage().c_str());
+    }
+    return PERMISSION_DENIED;
+}
+
+} // namespace android
diff --git a/libs/permission/include/android/permission/PermissionChecker.h b/libs/permission/include/android/permission/PermissionChecker.h
new file mode 100644
index 0000000..20ab51f
--- /dev/null
+++ b/libs/permission/include/android/permission/PermissionChecker.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 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/content/AttributionSourceState.h>
+#include <android/permission/IPermissionChecker.h>
+
+#include <utils/threads.h>
+
+#include <optional>
+
+#ifdef __ANDROID_VNDK__
+#error "This header is not visible to vendors"
+#endif
+
+// ---------------------------------------------------------------------------
+namespace android {
+
+using android::content::AttributionSourceState;
+using android::permission::IPermissionChecker;
+
+class PermissionChecker
+{
+public:
+
+    enum PermissionResult {
+
+        /**
+         * The permission is granted.
+         */
+        PERMISSION_GRANTED = IPermissionChecker::PERMISSION_GRANTED,
+
+        /**
+         * The permission is denied. Applicable only to runtime and app op permissions.
+         *
+         * Returned when:
+         *   - the runtime permission is granted, but the corresponding app op is denied
+         *       for runtime permissions.
+         *   - the app ops is ignored for app op permissions.
+         *
+         */
+        PERMISSION_SOFT_DENIED = IPermissionChecker::PERMISSION_SOFT_DENIED,
+
+        /**
+         * The permission is denied.
+         *
+         * Returned when:
+         *  - the permission is denied for non app op permissions.
+         *  - the app op is denied or app op is AppOpsManager#MODE_DEFAULT and permission is denied.
+         */
+        PERMISSION_HARD_DENIED = IPermissionChecker::PERMISSION_HARD_DENIED
+    };
+
+    PermissionChecker();
+
+    /**
+     * Checks whether a given data access chain described by the given attribution source
+     * has a given permission and whether the app op that corresponds to this permission
+     * is allowed. Call this method if you are the datasource which would not blame you for
+     * access to the data since you are the data. Note that the attribution source chain
+     *
+     * NOTE: The attribution source should be for yourself with its next attribution
+     * source being the app that would receive the data from you.
+     *
+     * NOTE: Use this method only for permission checks at the point where you will deliver
+     * the permission protected data to clients.
+     *
+     * @param permission The permission to check.
+     * @param attributionSource The attribution chain to check.
+     * @param message A message describing the reason the permission was checked.
+     * @return The permission check result which is either PERMISSION_GRANTED,
+     *     or PERMISSION_SOFT_DENIED or PERMISSION_HARD_DENIED.
+     */
+    PermissionChecker::PermissionResult checkPermissionForDataDeliveryFromDatasource(
+            const String16& permission, AttributionSourceState& attributionSource,
+            const String16& message);
+
+   /**
+     * Checks whether a given data access chain described by the given attribution source
+     * has a given permission and whether the app op that corresponds to this permission
+     * is allowed. The app ops are also marked as started. This is useful for long running
+     * permissions like camera and microphone.
+     *
+     * NOTE: The attribution source should be for yourself with its next attribution
+     * source being the app that would receive the data from you.
+     *
+     * NOTE: Use this method only for permission checks at the point where you will deliver
+     * the permission protected data to clients.
+     *
+     * @param permission The permission to check.
+     * @param attributionSource The attribution chain to check.
+     * @param message A message describing the reason the permission was checked.
+     * @return The permission check result which is either PERMISSION_GRANTED,
+     *     or PERMISSION_SOFT_DENIED or PERMISSION_HARD_DENIED.
+     */
+    PermissionResult checkPermissionForStartDataDeliveryFromDatasource(
+            const String16& permission, AttributionSourceState& attributionSource,
+            const String16& message);
+
+    /**
+     * Finishes an ongoing op for data access chain described by the given
+     * attribution source.
+     *
+     * @param op The op to finish.
+     * @param attributionSource The attribution chain for which to finish data delivery.
+     */
+    void finishDataDelivery(const String16& op, AttributionSourceState& attributionSource);
+
+private:
+    Mutex mLock;
+    sp<IPermissionChecker> mService;
+    sp<IPermissionChecker> getService();
+
+    int32_t checkPermission(const String16& permission, AttributionSourceState& attributionSource,
+            const String16& message, bool forDataDelivery, bool startDataDelivery,
+            bool fromDatasource);
+};
+
+
+} // namespace android
+
+// ---------------------------------------------------------------------------
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index c048cbe..e3d705f 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -146,7 +146,8 @@
         OP_UWB_RANGING = 112,
         OP_ACTIVITY_RECOGNITION_SOURCE = 113,
         OP_BLUETOOTH_ADVERTISE = 114,
-        _NUM_OP = 115
+        OP_RECORD_INCOMING_PHONE_AUDIO = 115,
+        _NUM_OP = 116
     };
 
     AppOpsManager();
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index f395ab4..570c7bc 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -95,6 +95,7 @@
         "skia/debug/SkiaMemoryReporter.cpp",
         "skia/filters/BlurFilter.cpp",
         "skia/filters/LinearEffect.cpp",
+        "skia/filters/StretchShaderFactory.cpp"
     ],
 }
 
@@ -117,7 +118,7 @@
     include_dirs: [
         "external/skia/src/gpu",
     ],
-    whole_static_libs: ["libskia"],
+    whole_static_libs: ["libskia_renderengine"],
     lto: {
         thin: true,
     },
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index d87315f..9400037 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -249,7 +249,7 @@
     // initialize EGL for the default display
     EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
     if (!eglInitialize(display, nullptr, nullptr)) {
-        LOG_ALWAYS_FATAL("failed to initialize EGL");
+        LOG_ALWAYS_FATAL("failed to initialize EGL. EGL error=0x%x", eglGetError());
     }
 
     const auto eglVersion = eglQueryString(display, EGL_VERSION);
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index c54c5ba..6e98352 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -156,6 +156,10 @@
 
     std::vector<BlurRegion> blurRegions;
 
+    // Transform matrix used to convert the blurRegions geometry into the same
+    // coordinate space as LayerSettings.geometry
+    mat4 blurRegionTransform = mat4();
+
     StretchEffect stretchEffect;
 
     // Name associated with the layer for debugging purposes.
@@ -218,7 +222,9 @@
             lhs.sourceDataspace == rhs.sourceDataspace &&
             lhs.colorTransform == rhs.colorTransform &&
             lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow &&
-            lhs.backgroundBlurRadius == rhs.backgroundBlurRadius;
+            lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
+            lhs.blurRegionTransform == rhs.blurRegionTransform &&
+            lhs.stretchEffect == rhs.stretchEffect;
 }
 
 // Defining PrintTo helps with Google Tests.
@@ -268,6 +274,21 @@
     *os << "\n}";
 }
 
+static inline void PrintTo(const StretchEffect& effect, ::std::ostream* os) {
+    *os << "StretchEffect {";
+    *os << "\n     .width = " << effect.width;
+    *os << "\n     .height = " << effect.height;
+    *os << "\n     .vectorX = " << effect.vectorX;
+    *os << "\n     .vectorY = " << effect.vectorY;
+    *os << "\n     .maxAmountX = " << effect.maxAmountX;
+    *os << "\n     .maxAmountY = " << effect.maxAmountY;
+    *os << "\n     .mappedLeft = " << effect.mappedChildBounds.left;
+    *os << "\n     .mappedTop = " << effect.mappedChildBounds.top;
+    *os << "\n     .mappedRight = " << effect.mappedChildBounds.right;
+    *os << "\n     .mappedBottom = " << effect.mappedChildBounds.bottom;
+    *os << "\n}";
+}
+
 static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
     *os << "LayerSettings {";
     *os << "\n    .geometry = ";
@@ -286,6 +307,8 @@
     }
     *os << "\n    .shadow = ";
     PrintTo(settings.shadow, os);
+    *os << "\n    .stretchEffect = ";
+    PrintTo(settings.stretchEffect, os);
     *os << "\n}";
 }
 
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index a0da888..6ab93df 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -42,10 +42,8 @@
                               0.f,   0.f, 1.f, 0.f,
                               0.f,   0.f, 0.f, 1.f);
 // clang-format on
-// When choosing dataspaces below, whether the match the destination or not determined whether
-// a color correction effect is added to the shader. There may be other additional shader details
-// for particular color spaces.
-// TODO(b/184842383) figure out which color related shaders are necessary
+// When setting layer.sourceDataspace, whether it matches the destination or not determines whether
+// a color correction effect is added to the shader.
 constexpr auto kDestDataSpace = ui::Dataspace::SRGB;
 constexpr auto kOtherDataSpace = ui::Dataspace::DISPLAY_P3;
 } // namespace
@@ -120,20 +118,24 @@
 
     // Test both drawRect and drawRRect
     auto layers = std::vector<const LayerSettings*>{&layer};
-    for (bool identity : {true, false}) {
-        layer.geometry.positionTransform = identity ? mat4() : kScaleAndTranslate;
-        // Corner radii less than 0.5 creates a special shader. This likely occurs in real usage
-        // due to animating corner radius.
-        // For the non-idenity matrix, only the large corner radius will create a new shader.
-        for (float roundedCornersRadius : identity ? threeCornerRadii : oneCornerRadius) {
-            // roundedCornersCrop is always set, but it is this radius that triggers the behavior
-            layer.geometry.roundedCornersRadius = roundedCornersRadius;
-            for (bool isOpaque : {true, false}) {
-                layer.source.buffer.isOpaque = isOpaque;
-                for (auto alpha : {half(.23999f), half(1.0f)}) {
-                    layer.alpha = alpha;
-                    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                             base::unique_fd(), nullptr);
+    for (auto dataspace : {kDestDataSpace, kOtherDataSpace}) {
+        layer.sourceDataspace = dataspace;
+        for (bool identity : {true, false}) {
+            layer.geometry.positionTransform = identity ? mat4() : kScaleAndTranslate;
+            // Corner radii less than 0.5 creates a special shader. This likely occurs in real usage
+            // due to animating corner radius.
+            // For the non-idenity matrix, only the large corner radius will create a new shader.
+            for (float roundedCornersRadius : identity ? threeCornerRadii : oneCornerRadius) {
+                // roundedCornersCrop is always set, but it is this radius that triggers the
+                // behavior
+                layer.geometry.roundedCornersRadius = roundedCornersRadius;
+                for (bool isOpaque : {true, false}) {
+                    layer.source.buffer.isOpaque = isOpaque;
+                    for (auto alpha : {half(.23999f), half(1.0f)}) {
+                        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 377b6f8..540e36a 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -56,6 +56,7 @@
 #include "log/log_main.h"
 #include "skia/debug/SkiaCapture.h"
 #include "skia/debug/SkiaMemoryReporter.h"
+#include "skia/filters/StretchShaderFactory.h"
 #include "system/graphics-base-v1.0.h"
 
 namespace {
@@ -365,6 +366,10 @@
     return mProtectedEGLContext != EGL_NO_CONTEXT;
 }
 
+GrDirectContext* SkiaGLRenderEngine::getActiveGrContext() const {
+    return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
+}
+
 bool SkiaGLRenderEngine::useProtectedContext(bool useProtectedContext) {
     if (useProtectedContext == mInProtectedContext) {
         return true;
@@ -372,6 +377,12 @@
     if (useProtectedContext && !supportsProtectedContent()) {
         return false;
     }
+
+    // release any scratch resources before switching into a new mode
+    if (getActiveGrContext()) {
+        getActiveGrContext()->purgeUnlockedResources(true);
+    }
+
     const EGLSurface surface =
             useProtectedContext ? mProtectedPlaceholderSurface : mPlaceholderSurface;
     const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
@@ -379,6 +390,11 @@
 
     if (success) {
         mInProtectedContext = useProtectedContext;
+        // given that we are sharing the same thread between two GrContexts we need to
+        // make sure that the thread state is reset when switching between the two.
+        if (getActiveGrContext()) {
+            getActiveGrContext()->resetContext();
+        }
     }
     return success;
 }
@@ -489,18 +505,23 @@
     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.
+    const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
+    if (isProtectedBuffer) {
+        return;
+    }
     ATRACE_CALL();
 
     // We need to switch the currently bound context if the buffer is protected but the current
     // context is not. The current state must then be restored after the buffer is cached.
     const bool protectedContextState = mInProtectedContext;
-    if (!useProtectedContext(protectedContextState ||
-                             (buffer->getUsage() & GRALLOC_USAGE_PROTECTED))) {
+    if (!useProtectedContext(protectedContextState || isProtectedBuffer)) {
         ALOGE("Attempting to cache a buffer into a different context than what is currently bound");
         return;
     }
 
-    auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
+    auto grContext = getActiveGrContext();
     auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
@@ -508,7 +529,7 @@
 
     if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
         std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext.get(),
+                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
                                                                buffer->toAHardwareBuffer(),
                                                                isRenderable);
         cache.insert({buffer->getId(), imageTextureRef});
@@ -541,14 +562,19 @@
     }
 }
 
-sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> shader,
-                                                              const LayerSettings* layer,
-                                                              const DisplaySettings& display,
-                                                              bool undoPremultipliedAlpha,
-                                                              bool requiresLinearEffect) {
-    if (layer->stretchEffect.hasEffect()) {
-        // TODO: Implement
+sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(
+        sk_sp<SkShader> shader,
+        const LayerSettings* layer, const DisplaySettings& display, bool undoPremultipliedAlpha,
+        bool requiresLinearEffect) {
+    const auto stretchEffect = layer->stretchEffect;
+    if (stretchEffect.hasEffect()) {
+        const auto targetBuffer = layer->source.buffer.buffer;
+        const auto graphicsBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+        if (graphicsBuffer && shader) {
+            shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
+        }
     }
+
     if (requiresLinearEffect) {
         const ui::Dataspace inputDataspace =
                 mUseColorManagement ? layer->sourceDataspace : ui::Dataspace::UNKNOWN;
@@ -634,6 +660,44 @@
     int mSaveCount;
 };
 
+void drawStretch(const SkRect& bounds, const StretchEffect& stretchEffect,
+                 SkCanvas* canvas, const SkPaint& paint) {
+    float top = bounds.top();
+    float left = bounds.left();
+    float bottom = bounds.bottom();
+    float right = bounds.right();
+    // Adjust the drawing bounds based on the stretch itself.
+    float stretchOffsetX =
+        round(bounds.width() * stretchEffect.getStretchWidthMultiplier());
+    float stretchOffsetY =
+        round(bounds.height() * stretchEffect.getStretchHeightMultiplier());
+    if (stretchEffect.vectorY < 0.f) {
+        top -= stretchOffsetY;
+    } else if (stretchEffect.vectorY > 0.f){
+        bottom += stretchOffsetY;
+    }
+
+    if (stretchEffect.vectorX < 0.f) {
+        left -= stretchOffsetX;
+    } else if (stretchEffect.vectorX > 0.f) {
+        right += stretchOffsetX;
+    }
+
+    auto stretchBounds = SkRect::MakeLTRB(left, top, right, bottom);
+    canvas->drawRect(stretchBounds, paint);
+}
+
+static SkRRect getBlurRRect(const BlurRegion& region) {
+    const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom);
+    const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL),
+                               SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR),
+                               SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR),
+                               SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)};
+    SkRRect roundedRect;
+    roundedRect.setRectRadii(rect, radii);
+    return roundedRect;
+}
+
 status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                         const std::vector<const LayerSettings*>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
@@ -662,7 +726,7 @@
 
     validateOutputBufferUsage(buffer->getBuffer());
 
-    auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
+    auto grContext = getActiveGrContext();
     auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
 
     std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef;
@@ -670,7 +734,7 @@
         surfaceTextureRef = it->second;
     } else {
         surfaceTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext.get(),
+                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
                                                                buffer->getBuffer()
                                                                        ->toAHardwareBuffer(),
                                                                true);
@@ -678,8 +742,7 @@
 
     const ui::Dataspace dstDataspace =
             mUseColorManagement ? display.outputDataspace : ui::Dataspace::UNKNOWN;
-    sk_sp<SkSurface> dstSurface =
-            surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext.get());
+    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext);
 
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
@@ -809,7 +872,7 @@
         // Layers have a local transform that should be applied to them
         canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
 
-        const auto bounds = getSkRect(layer->geometry.boundaries);
+        const auto [bounds, roundRectClip] = getBoundsAndClip(layer);
         if (mBlurFilter && layerHasBlur(layer)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
@@ -819,30 +882,40 @@
                 blurInput = activeSurface->makeImageSnapshot();
             }
             // rect to be blurred in the coordinate space of blurInput
-            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds);
+            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
+
+            // if the clip needs to be applied then apply it now and make sure
+            // it is restored before we attempt to draw any shadows.
+            SkAutoCanvasRestore acr(canvas, true);
+            if (!roundRectClip.isEmpty()) {
+                canvas->clipRRect(roundRectClip, true);
+            }
 
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
                 if (layer->backgroundBlurRadius > 0) {
                     ATRACE_NAME("BackgroundBlur");
                     auto blurredImage =
-                            mBlurFilter->generate(grContext.get(), layer->backgroundBlurRadius,
-                                                  blurInput, blurRect);
+                            mBlurFilter->generate(grContext, layer->backgroundBlurRadius, blurInput,
+                                                  blurRect);
 
                     cachedBlurs[layer->backgroundBlurRadius] = blurredImage;
 
-                    mBlurFilter->drawBlurRegion(canvas, getBlurRegion(layer), blurRect,
-                                                blurredImage, blurInput);
+                    mBlurFilter->drawBlurRegion(canvas, bounds, layer->backgroundBlurRadius, 1.0f,
+                                                blurRect, blurredImage, blurInput);
                 }
+
+                canvas->concat(getSkM44(layer->blurRegionTransform).asM33());
                 for (auto region : layer->blurRegions) {
                     if (cachedBlurs[region.blurRadius] == nullptr) {
                         ATRACE_NAME("BlurRegion");
                         cachedBlurs[region.blurRadius] =
-                                mBlurFilter->generate(grContext.get(), region.blurRadius, blurInput,
+                                mBlurFilter->generate(grContext, region.blurRadius, blurInput,
                                                       blurRect);
                     }
 
-                    mBlurFilter->drawBlurRegion(canvas, region, blurRect,
+                    mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius,
+                                                region.alpha, blurRect,
                                                 cachedBlurs[region.blurRadius], blurInput);
                 }
             }
@@ -855,7 +928,7 @@
         if (layer->shadow.length > 0) {
             const auto rect = layer->geometry.roundedCornersRadius > 0
                     ? getSkRect(layer->geometry.roundedCornersCrop)
-                    : bounds;
+                    : bounds.rect();
             // This would require a new parameter/flag to SkShadowUtils::DrawShadow
             LOG_ALWAYS_FATAL_IF(layer->disableBlending, "Cannot disableBlending with a shadow");
             drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
@@ -895,7 +968,7 @@
                 // we didn't find anything in the cache then we intentionally did not cache this
                 // buffer's resources.
                 imageTextureRef = std::make_shared<
-                        AutoBackendTexture::LocalRef>(grContext.get(),
+                        AutoBackendTexture::LocalRef>(grContext,
                                                       item.buffer->getBuffer()->toAHardwareBuffer(),
                                                       false);
             }
@@ -904,7 +977,7 @@
                     imageTextureRef->makeImage(layerDataspace,
                                                item.usePremultipliedAlpha ? kPremul_SkAlphaType
                                                                           : kUnpremul_SkAlphaType,
-                                               grContext.get());
+                                               grContext);
 
             auto texMatrix = getSkM44(item.textureTransform).asM33();
             // textureTansform was intended to be passed directly into a shader, so when
@@ -920,7 +993,7 @@
             // The shader does not respect the translation, so we add it to the texture
             // transform for the SkImage. This will make sure that the correct layer contents
             // are drawn in the correct part of the screen.
-            matrix.postTranslate(layer->geometry.boundaries.left, layer->geometry.boundaries.top);
+            matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop);
 
             sk_sp<SkShader> shader;
 
@@ -982,11 +1055,25 @@
 
         paint.setColorFilter(displayColorTransform);
 
-        if (layer->geometry.roundedCornersRadius > 0) {
+        if (!roundRectClip.isEmpty()) {
+            canvas->clipRRect(roundRectClip, true);
+        }
+
+        if (!bounds.isRect()) {
             paint.setAntiAlias(true);
-            canvas->drawRRect(getRoundedRect(layer), paint);
+            canvas->drawRRect(bounds, paint);
         } else {
-            canvas->drawRect(bounds, paint);
+            auto& stretchEffect = layer->stretchEffect;
+            // TODO (njawad) temporarily disable manipulation of geometry
+            //  the layer bounds will be updated in HWUI instead of RenderEngine
+            //  in a subsequent CL
+            // Keep the method call in a dead code path to make -Werror happy
+            // with unused methods
+            if (stretchEffect.hasEffect() && /* DISABLES CODE */ (false)) {
+                drawStretch(bounds.rect(), stretchEffect, canvas, paint);
+            } else {
+                canvas->drawRect(bounds.rect(), paint);
+            }
         }
         if (kFlushAfterEveryLayer) {
             ATRACE_NAME("flush surface");
@@ -1034,25 +1121,76 @@
     return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
 }
 
-inline SkRRect SkiaGLRenderEngine::getRoundedRect(const LayerSettings* layer) {
-    const auto rect = getSkRect(layer->geometry.roundedCornersCrop);
+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;
-    return SkRRect::MakeRectXY(rect, cornerRadius, cornerRadius);
-}
 
-inline BlurRegion SkiaGLRenderEngine::getBlurRegion(const LayerSettings* layer) {
-    const auto rect = getSkRect(layer->geometry.boundaries);
-    const auto cornersRadius = layer->geometry.roundedCornersRadius;
-    return BlurRegion{.blurRadius = static_cast<uint32_t>(layer->backgroundBlurRadius),
-                      .cornerRadiusTL = cornersRadius,
-                      .cornerRadiusTR = cornersRadius,
-                      .cornerRadiusBL = cornersRadius,
-                      .cornerRadiusBR = cornersRadius,
-                      .alpha = 1,
-                      .left = static_cast<int>(rect.fLeft),
-                      .top = static_cast<int>(rect.fTop),
-                      .right = static_cast<int>(rect.fRight),
-                      .bottom = static_cast<int>(rect.fBottom)};
+    SkRRect clip;
+    if (cornerRadius > 0) {
+        // it the crop and the bounds are equivalent then we don't need a clip
+        if (bounds == crop) {
+            return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip};
+        }
+
+        // This makes an effort to speed up common, simple bounds + clip combinations by
+        // converting them to a single RRect draw. It is possible there are other cases
+        // that can be converted.
+        if (crop.contains(bounds)) {
+            bool intersectionIsRoundRect = true;
+            // check each cropped corner to ensure that it exactly matches the crop or is full
+            SkVector radii[4];
+
+            const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
+
+            // compute the UpperLeft corner radius
+            if (bounds.fLeft == crop.fLeft && bounds.fTop == crop.fTop) {
+                radii[0].set(cornerRadius, cornerRadius);
+            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fTop > insetCrop.fTop) {
+                radii[0].set(0, 0);
+            } else {
+                intersectionIsRoundRect = false;
+            }
+            // compute the UpperRight corner radius
+            if (bounds.fRight == crop.fRight && bounds.fTop == crop.fTop) {
+                radii[1].set(cornerRadius, cornerRadius);
+            } else if (bounds.fRight < insetCrop.fRight && bounds.fTop > insetCrop.fTop) {
+                radii[1].set(0, 0);
+            } else {
+                intersectionIsRoundRect = false;
+            }
+            // compute the BottomRight corner radius
+            if (bounds.fRight == crop.fRight && bounds.fBottom == crop.fBottom) {
+                radii[2].set(cornerRadius, cornerRadius);
+            } else if (bounds.fRight < insetCrop.fRight && bounds.fBottom < insetCrop.fBottom) {
+                radii[2].set(0, 0);
+            } else {
+                intersectionIsRoundRect = false;
+            }
+            // compute the BottomLeft corner radius
+            if (bounds.fLeft == crop.fLeft && bounds.fBottom == crop.fBottom) {
+                radii[3].set(cornerRadius, cornerRadius);
+            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fBottom < insetCrop.fBottom) {
+                radii[3].set(0, 0);
+            } else {
+                intersectionIsRoundRect = false;
+            }
+
+            if (intersectionIsRoundRect) {
+                SkRRect intersectionBounds;
+                intersectionBounds.setRectRadii(bounds, radii);
+                return {intersectionBounds, clip};
+            }
+        }
+
+        // we didn't it any of our fast paths so set the clip to the cropRect
+        clip.setRectXY(crop, cornerRadius, cornerRadius);
+    }
+
+    // if we hit this point then we either don't have rounded corners or we are going to rely
+    // on the clip to round the corners for us
+    return {SkRRect::MakeRect(bounds), clip};
 }
 
 inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings* layer) {
@@ -1224,13 +1362,11 @@
     const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
 
     // start by resizing the current context
-    auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
-    grContext->setResourceCacheLimit(maxResourceBytes);
+    getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
 
     // if it is possible to switch contexts then we will resize the other context
     if (useProtectedContext(!mInProtectedContext)) {
-        grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
-        grContext->setResourceCacheLimit(maxResourceBytes);
+        getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
         // reset back to the initial context that was active when this method was called
         useProtectedContext(!mInProtectedContext);
     }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index e71c560..d9caf35 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -39,6 +39,7 @@
 #include "debug/SkiaCapture.h"
 #include "filters/BlurFilter.h"
 #include "filters/LinearEffect.h"
+#include "filters/StretchShaderFactory.h"
 
 namespace android {
 namespace renderengine {
@@ -87,12 +88,12 @@
                                                          int hwcFormat, Protection protection);
     inline SkRect getSkRect(const FloatRect& layer);
     inline SkRect getSkRect(const Rect& layer);
-    inline SkRRect getRoundedRect(const LayerSettings* layer);
-    inline BlurRegion getBlurRegion(const LayerSettings* layer);
+    inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const LayerSettings* layer);
     inline bool layerHasBlur(const LayerSettings* layer);
     inline SkColor getSkColor(const vec4& color);
     inline SkM44 getSkM44(const mat4& matrix);
     inline SkPoint3 getSkPoint3(const vec3& vector);
+    inline GrDirectContext* getActiveGrContext() const;
 
     base::unique_fd flush();
     bool waitFence(base::unique_fd fenceFd);
@@ -101,7 +102,8 @@
                     const ShadowSettings& shadowSettings);
     // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
     // Otherwise it returns the input shader.
-    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader, const LayerSettings* layer,
+    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader,
+                                              const LayerSettings* layer,
                                               const DisplaySettings& display,
                                               bool undoPremultipliedAlpha,
                                               bool requiresLinearEffect);
@@ -116,14 +118,20 @@
     const PixelFormat mDefaultPixelFormat;
     const bool mUseColorManagement;
 
+    // Identifier used or various mappings of layers to various
+    // textures or shaders
+    using LayerId = uint64_t;
+
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
-    std::unordered_map<uint64_t, int32_t> mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex);
+    std::unordered_map<LayerId, int32_t> mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex);
     // Cache of GL textures that we'll store per GraphicBuffer ID, sliced by GPU context.
-    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+    std::unordered_map<LayerId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
-    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>>
+    std::unordered_map<LayerId, std::shared_ptr<AutoBackendTexture::LocalRef>>
             mProtectedTextureCache GUARDED_BY(mRenderingMutex);
     std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
+
+    StretchShaderFactory mStretchShaderFactory;
     // Mutex guarding rendering operations, so that:
     // 1. GL operations aren't interleaved, and
     // 2. Internal state related to rendering that is potentially modified by
diff --git a/libs/renderengine/skia/debug/record.sh b/libs/renderengine/skia/debug/record.sh
index bc406d9..25c8cef 100755
--- a/libs/renderengine/skia/debug/record.sh
+++ b/libs/renderengine/skia/debug/record.sh
@@ -31,7 +31,7 @@
 # give the device time to both record, and starting writing the file.
 # Total time needed to write the file depends on how much data was recorded.
 # the loop at the end waits for this.
-sleep $(($1 / 1000 + 2));
+sleep $(($1 / 1000 + 4));
 
 # There is no guarantee that at least one frame passed through renderengine during that time
 # but as far as I know it always at least writes a 0-byte file with a new name, unless it crashes
@@ -54,7 +54,7 @@
 
 mskp_size=$(adb_filesize "/data/user/$name")
 if [[ $mskp_size = "0" ]]; then
-  echo "Empty file, probably no RenderEngine activity during recording period."
+  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
 
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 2028def..4ad6e94 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -86,8 +86,8 @@
     float radiusByPasses = tmpRadius / (float)numberOfPasses;
 
     // create blur surface with the bit depth and colorspace of the original surface
-    SkImageInfo scaledInfo = input->imageInfo().makeWH(blurRect.width() * kInputScale,
-                                                       blurRect.height() * kInputScale);
+    SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
+                                                       std::ceil(blurRect.height() * kInputScale));
 
     const float stepX = radiusByPasses;
     const float stepY = radiusByPasses;
@@ -105,7 +105,7 @@
             input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
     blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInputScale, stepY * kInputScale};
     blurBuilder.uniform("in_maxSizeXY") =
-            SkV2{blurRect.width() * kInputScale - 1, blurRect.height() * kInputScale - 1};
+            SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
 
     sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
 
@@ -116,7 +116,7 @@
                 tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
         blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale, stepY * stepScale};
         blurBuilder.uniform("in_maxSizeXY") =
-                SkV2{blurRect.width() * kInputScale - 1, blurRect.height() * kInputScale - 1};
+                SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
         tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
     }
 
@@ -139,23 +139,21 @@
     return matrix;
 }
 
-void BlurFilter::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
+void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
+                                const uint32_t blurRadius, const float blurAlpha,
                                 const SkRect& blurRect, sk_sp<SkImage> blurredImage,
                                 sk_sp<SkImage> input) {
     ATRACE_CALL();
 
     SkPaint paint;
-    paint.setAlphaf(effectRegion.alpha);
-    if (effectRegion.alpha == 1.0f) {
-        paint.setBlendMode(SkBlendMode::kSrc);
-    }
+    paint.setAlphaf(blurAlpha);
 
     const auto blurMatrix = getShaderTransform(canvas, blurRect, kInverseInputScale);
     SkSamplingOptions linearSampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
     const auto blurShader = blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                                      linearSampling, &blurMatrix);
 
-    if (effectRegion.blurRadius < kMaxCrossFadeRadius) {
+    if (blurRadius < kMaxCrossFadeRadius) {
         // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
         // case you might expect the matrix to simply be the canvas matrix.
         SkMatrix inputMatrix;
@@ -168,30 +166,21 @@
         blurBuilder.child("originalInput") =
                 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                   inputMatrix);
-        blurBuilder.uniform("mixFactor") = effectRegion.blurRadius / kMaxCrossFadeRadius;
+        blurBuilder.uniform("mixFactor") = blurRadius / kMaxCrossFadeRadius;
 
         paint.setShader(blurBuilder.makeShader(nullptr, true));
     } else {
         paint.setShader(blurShader);
     }
 
-    // TODO we should AA at least the drawRoundRect which would mean no SRC blending
-    // TODO this round rect calculation doesn't match the one used to draw in RenderEngine
-    auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
-                                 effectRegion.bottom);
-
-    if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
-        effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
-        const SkVector radii[4] =
-                {SkVector::Make(effectRegion.cornerRadiusTL, effectRegion.cornerRadiusTL),
-                 SkVector::Make(effectRegion.cornerRadiusTR, effectRegion.cornerRadiusTR),
-                 SkVector::Make(effectRegion.cornerRadiusBL, effectRegion.cornerRadiusBL),
-                 SkVector::Make(effectRegion.cornerRadiusBR, effectRegion.cornerRadiusBR)};
-        SkRRect roundedRect;
-        roundedRect.setRectRadii(rect, radii);
-        canvas->drawRRect(roundedRect, paint);
+    if (effectRegion.isRect()) {
+        if (blurAlpha == 1.0f) {
+            paint.setBlendMode(SkBlendMode::kSrc);
+        }
+        canvas->drawRect(effectRegion.rect(), paint);
     } else {
-        canvas->drawRect(rect, paint);
+        paint.setAntiAlias(true);
+        canvas->drawRRect(effectRegion, paint);
     }
 }
 
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index 731ba11..a8e6dd7 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -20,7 +20,6 @@
 #include <SkImage.h>
 #include <SkRuntimeEffect.h>
 #include <SkSurface.h>
-#include <ui/BlurRegion.h>
 
 using namespace std;
 
@@ -52,8 +51,19 @@
     sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const;
 
-    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& blurRect,
-                        sk_sp<SkImage> blurredImage, sk_sp<SkImage> input);
+    /**
+     * Draw the blurred content (from the generate method) into the canvas.
+     * @param canvas is the destination/output for the blur
+     * @param effectRegion the RoundRect in canvas coordinates that determines the blur coverage
+     * @param blurRadius radius of the blur used to determine the intensity of the crossfade effect
+     * @param blurAlpha alpha value applied to the effectRegion when the blur is drawn
+     * @param blurRect bounds of the blurredImage translated into canvas coordinates
+     * @param blurredImage down-sampled blurred content that was produced by the generate() method
+     * @param input original unblurred input that is used to crossfade with the blurredImage
+     */
+    void drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion, const uint32_t blurRadius,
+                        const float blurAlpha, const SkRect& blurRect, sk_sp<SkImage> blurredImage,
+                        sk_sp<SkImage> input);
 
 private:
     sk_sp<SkRuntimeEffect> mBlurEffect;
diff --git a/libs/renderengine/skia/filters/StretchShaderFactory.cpp b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
new file mode 100644
index 0000000..9b62789
--- /dev/null
+++ b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+#include "StretchShaderFactory.h"
+#include <SkImageFilter.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include "log/log.h"
+#include <memory>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+static const SkString stretchShader = SkString(R"(
+    uniform shader uContentTexture;
+
+    // multiplier to apply to scale effect
+    uniform float uMaxStretchIntensity;
+
+    // Maximum percentage to stretch beyond bounds  of target
+    uniform float uStretchAffectedDistX;
+    uniform float uStretchAffectedDistY;
+
+    // Distance stretched as a function of the normalized overscroll times
+    // scale intensity
+    uniform float uDistanceStretchedX;
+    uniform float uDistanceStretchedY;
+    uniform float uInverseDistanceStretchedX;
+    uniform float uInverseDistanceStretchedY;
+    uniform float uDistDiffX;
+
+    // Difference between the peak stretch amount and overscroll amount normalized
+    uniform float uDistDiffY;
+
+    // Horizontal offset represented as a ratio of pixels divided by the target width
+    uniform float uScrollX;
+    // Vertical offset represented as a ratio of pixels divided by the target height
+    uniform float uScrollY;
+
+    // Normalized overscroll amount in the horizontal direction
+    uniform float uOverscrollX;
+
+    // Normalized overscroll amount in the vertical direction
+    uniform float uOverscrollY;
+    uniform float viewportWidth; // target height in pixels
+    uniform float viewportHeight; // target width in pixels
+
+    // uInterpolationStrength is the intensity of the interpolation.
+    // if uInterpolationStrength is 0, then the stretch is constant for all the
+    // uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity
+    // is interpolated based on the pixel position in the uStretchAffectedDist area;
+    // The closer we are from the scroll anchor point, the more it stretches,
+    // and the other way around.
+    uniform float uInterpolationStrength;
+
+    float easeInCubic(float t, float d) {
+        float tmp = t * d;
+        return tmp * tmp * tmp;
+    }
+
+    float computeOverscrollStart(
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float interpolationStrength
+    ) {
+        float offsetPos = uStretchAffectedDist - inPos;
+        float posBasedVariation = mix(
+                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+        float stretchIntensity = overscroll * posBasedVariation;
+        return distanceStretched - (offsetPos / (1. + stretchIntensity));
+    }
+
+    float computeOverscrollEnd(
+        float inPos,
+        float overscroll,
+        float reverseStretchDist,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float interpolationStrength
+    ) {
+        float offsetPos = inPos - reverseStretchDist;
+        float posBasedVariation = mix(
+                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+        float stretchIntensity = (-overscroll) * posBasedVariation;
+        return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
+    }
+
+    // Prefer usage of return values over out parameters as it enables
+    // SKSL to properly inline method calls and works around potential GPU
+    // driver issues on Wembly. See b/182566543 for details
+    float computeOverscroll(
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float distanceDiff,
+        float interpolationStrength
+    ) {
+      float outPos = inPos;
+      // overscroll is provided via uniform so there is no concern
+      // for potential incoherent branches
+      if (overscroll > 0) {
+            if (inPos <= uStretchAffectedDist) {
+                outPos = computeOverscrollStart(
+                  inPos,
+                  overscroll,
+                  uStretchAffectedDist,
+                  uInverseStretchAffectedDist,
+                  distanceStretched,
+                  interpolationStrength
+                );
+            } else if (inPos >= distanceStretched) {
+                outPos = distanceDiff + inPos;
+            }
+        }
+        if (overscroll < 0) {
+            float stretchAffectedDist = 1. - uStretchAffectedDist;
+            if (inPos >= stretchAffectedDist) {
+                outPos = computeOverscrollEnd(
+                  inPos,
+                  overscroll,
+                  stretchAffectedDist,
+                  uStretchAffectedDist,
+                  uInverseStretchAffectedDist,
+                  distanceStretched,
+                  interpolationStrength
+                );
+            } else if (inPos < stretchAffectedDist) {
+                outPos = -distanceDiff + inPos;
+            }
+        }
+        return outPos;
+    }
+
+    vec4 main(vec2 coord) {
+        // Normalize SKSL pixel coordinate into a unit vector
+        float inU = coord.x / viewportWidth;
+        float inV = coord.y / viewportHeight;
+        float outU;
+        float outV;
+        float stretchIntensity;
+        // Add the normalized scroll position within scrolling list
+        inU += uScrollX;
+        inV += uScrollY;
+        outU = inU;
+        outV = inV;
+        outU = computeOverscroll(
+            inU,
+            uOverscrollX,
+            uStretchAffectedDistX,
+            uInverseDistanceStretchedX,
+            uDistanceStretchedX,
+            uDistDiffX,
+            uInterpolationStrength
+        );
+        outV = computeOverscroll(
+            inV,
+            uOverscrollY,
+            uStretchAffectedDistY,
+            uInverseDistanceStretchedY,
+            uDistanceStretchedY,
+            uDistDiffY,
+            uInterpolationStrength
+        );
+        coord.x = (outU - uScrollX) * viewportWidth;
+        coord.y = (outV - uScrollY) * viewportHeight;
+        return sample(uContentTexture, coord);
+    })");
+
+const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
+
+sk_sp<SkShader> StretchShaderFactory::createSkShader(const sk_sp<SkShader>& inputShader,
+                                                     const StretchEffect& stretchEffect) {
+    if (!stretchEffect.hasEffect()) {
+        return nullptr;
+    }
+
+    float viewportWidth = stretchEffect.width;
+    float viewportHeight = stretchEffect.height;
+    float normOverScrollDistX = stretchEffect.vectorX;
+    float normOverScrollDistY = stretchEffect.vectorY;
+    float distanceStretchedX =
+        StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
+    float distanceStretchedY =
+        StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
+    float inverseDistanceStretchedX =
+        1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float inverseDistanceStretchedY =
+        1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float diffX =
+        distanceStretchedX - StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float diffY =
+        distanceStretchedY - StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    auto& srcBounds = stretchEffect.mappedChildBounds;
+    float normalizedScrollX = srcBounds.left / viewportWidth;
+    float normalizedScrollY = srcBounds.top / viewportHeight;
+
+    if (mBuilder == nullptr) {
+        const static SkRuntimeEffect::Result instance =
+            SkRuntimeEffect::MakeForShader(stretchShader);
+        mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
+    }
+
+    mBuilder->child("uContentTexture") = inputShader;
+    mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
+    mBuilder->uniform("uStretchAffectedDistX").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
+    mBuilder->uniform("uStretchAffectedDistY").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
+    mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
+    mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
+    mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
+    mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
+    mBuilder->uniform("uDistDiffX").set(&diffX, 1);
+    mBuilder->uniform("uDistDiffY").set(&diffY, 1);
+    mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
+    mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
+    mBuilder->uniform("uScrollX").set(&normalizedScrollX, 1);
+    mBuilder->uniform("uScrollY").set(&normalizedScrollY, 1);
+    mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
+    mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
+
+    return mBuilder->makeShader(nullptr, false);
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/filters/StretchShaderFactory.h b/libs/renderengine/skia/filters/StretchShaderFactory.h
new file mode 100644
index 0000000..9c3ab7c
--- /dev/null
+++ b/libs/renderengine/skia/filters/StretchShaderFactory.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include <ui/StretchEffect.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+class StretchShaderFactory {
+public:
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& inputShader,
+                                   const StretchEffect& stretchEffect);
+
+private:
+    std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+};
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 34ef0a4..72e32ed 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -263,6 +263,11 @@
         }
     }
 
+    void expectBufferColor(const Point& point, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
+                           uint8_t tolerance = 0) {
+        expectBufferColor(Rect(point.x, point.y, point.x + 1, point.y + 1), r, g, b, a, tolerance);
+    }
+
     void expectBufferColor(const Rect& rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
                            uint8_t tolerance = 0) {
         auto colorCompare = [tolerance](const uint8_t* colorA, const uint8_t* colorB) {
@@ -487,6 +492,9 @@
     void fillBufferAndBlurBackground();
 
     template <typename SourceVariant>
+    void fillSmallLayerAndBlurBackground();
+
+    template <typename SourceVariant>
     void overlayCorners();
 
     void fillRedBufferTextureTransform();
@@ -966,11 +974,44 @@
     if (mRE->supportsBackgroundBlur()) {
         // blurred color (downsampling should result in the center color being close to 128)
         expectBufferColor(Rect(center - 1, center - 5, center + 1, center + 5), 128, 128, 0, 255,
-                          10 /* tolerance */);
+                          50 /* tolerance */);
     }
 }
 
 template <typename SourceVariant>
+void RenderEngineTest::fillSmallLayerAndBlurBackground() {
+    auto blurRadius = 50;
+    renderengine::DisplaySettings settings;
+    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+
+    std::vector<const renderengine::LayerSettings*> layers;
+
+    renderengine::LayerSettings backgroundLayer;
+    backgroundLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    backgroundLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+    SourceVariant::fillColor(backgroundLayer, 1.0f, 0.0f, 0.0f, this);
+    backgroundLayer.alpha = 1.0f;
+    layers.push_back(&backgroundLayer);
+
+    renderengine::LayerSettings blurLayer;
+    blurLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    blurLayer.geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f);
+    blurLayer.backgroundBlurRadius = blurRadius;
+    SourceVariant::fillColor(blurLayer, 0.0f, 0.0f, 1.0f, this);
+    blurLayer.alpha = 0;
+    layers.push_back(&blurLayer);
+
+    invokeDraw(settings, layers);
+
+    // Give a generous tolerance - the blur rectangle is very small and this test is
+    // mainly concerned with ensuring that there's no device failure.
+    expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), 255, 0, 0, 255,
+                      40 /* tolerance */);
+}
+
+template <typename SourceVariant>
 void RenderEngineTest::overlayCorners() {
     renderengine::DisplaySettings settings;
     settings.physicalDisplay = fullscreenRect();
@@ -1402,12 +1443,16 @@
     fillBufferColorTransformZeroLayerAlpha<ColorSourceVariant>();
 }
 
-// TODO(b/186010146): reenable once swiftshader is happy with this test
-TEST_P(RenderEngineTest, DISABLED_drawLayers_fillBufferAndBlurBackground_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
     initializeRenderEngine();
     fillBufferAndBlurBackground<ColorSourceVariant>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) {
+    initializeRenderEngine();
+    fillSmallLayerAndBlurBackground<ColorSourceVariant>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
     initializeRenderEngine();
     overlayCorners<ColorSourceVariant>();
@@ -1478,12 +1523,16 @@
     fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
-// TODO(b/186010146): reenable once swiftshader is happy with this test
-TEST_P(RenderEngineTest, DISABLED_drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
     initializeRenderEngine();
     fillBufferAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) {
+    initializeRenderEngine();
+    fillSmallLayerAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
     initializeRenderEngine();
     overlayCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
@@ -1554,12 +1603,16 @@
     fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
-// TODO(b/186010146): reenable once swiftshader is happy with this test
-TEST_P(RenderEngineTest, DISABLED_drawLayers_fillBufferAndBlurBackground_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
     initializeRenderEngine();
     fillBufferAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) {
+    initializeRenderEngine();
+    fillSmallLayerAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
     initializeRenderEngine();
     overlayCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
@@ -1839,6 +1892,51 @@
                       0, 255, 0, 255);
 }
 
+TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
+    initializeRenderEngine();
+
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+    std::vector<const renderengine::LayerSettings*> layers;
+
+    renderengine::LayerSettings redLayer;
+    redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+    redLayer.geometry.roundedCornersRadius = 5.0f;
+    redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
+    // Red background.
+    redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+    redLayer.alpha = 1.0f;
+
+    layers.push_back(&redLayer);
+
+    // Green layer with 1/2 size with parent crop rect.
+    renderengine::LayerSettings greenLayer = redLayer;
+    greenLayer.geometry.boundaries =
+            FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT / 2);
+    greenLayer.source.solidColor = half3(0.0f, 1.0f, 0.0f);
+
+    layers.push_back(&greenLayer);
+
+    invokeDraw(settings, layers);
+
+    // Due to roundedCornersRadius, the corners are untouched.
+    expectBufferColor(Point(0, 0), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0);
+    expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+
+    // top middle should be green and the bottom middle red
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 0), 0, 255, 0, 255);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255);
+
+    // the bottom edge of the green layer should not be rounded
+    expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255);
+}
+
 TEST_P(RenderEngineTest, testClear) {
     initializeRenderEngine();
 
diff --git a/libs/ui/include/ui/StretchEffect.h b/libs/ui/include/ui/StretchEffect.h
index 0803df3..cf08acb 100644
--- a/libs/ui/include/ui/StretchEffect.h
+++ b/libs/ui/include/ui/StretchEffect.h
@@ -25,31 +25,49 @@
 namespace android {
 
 struct StretchEffect : public LightFlattenablePod<StretchEffect> {
-    FloatRect area = {0, 0, 0, 0};
-    float vectorX = 0;
-    float vectorY = 0;
-    float maxAmount = 0;
+  constexpr static const float CONTENT_DISTANCE_STRETCHED = 1.f;
 
-    bool operator==(const StretchEffect& other) const {
-        return area == other.area && vectorX == other.vectorX && vectorY == other.vectorY &&
-                maxAmount == other.maxAmount;
+  float width = 0;
+  float height = 0;
+  float vectorX = 0;
+  float vectorY = 0;
+  float maxAmountX = 0;
+  float maxAmountY = 0;
+  FloatRect mappedChildBounds = {0, 0, 0, 0};
+
+  bool operator==(const StretchEffect& other) const {
+    return width == other.width && height == other.height &&
+        vectorX == other.vectorX &&
+        vectorY == other.vectorY &&
+        maxAmountX == other.maxAmountX &&
+        maxAmountY == other.maxAmountY &&
+        mappedChildBounds == other.mappedChildBounds;
+  }
+
+  static bool isZero(float value) {
+    constexpr float NON_ZERO_EPSILON = 0.001f;
+    return fabsf(value) <= NON_ZERO_EPSILON;
+  }
+
+  bool isNoOp() const { return isZero(vectorX) && isZero(vectorY); }
+
+  bool hasEffect() const { return !isNoOp(); }
+
+  void sanitize() {
+    // If the area is empty, or the max amount is zero, then reset back to defaults
+    if (width == 0.f || height == 0.f || isZero(maxAmountX) ||
+        isZero(maxAmountY)) {
+      *this = StretchEffect{};
     }
+  }
 
-    static bool isZero(float value) {
-        constexpr float NON_ZERO_EPSILON = 0.001f;
-        return fabsf(value) <= NON_ZERO_EPSILON;
-    }
+  float getStretchWidthMultiplier() const {
+      return CONTENT_DISTANCE_STRETCHED / (1.f + abs(vectorX));
+  }
 
-    bool isNoOp() const { return isZero(vectorX) && isZero(vectorY); }
-
-    bool hasEffect() const { return !isNoOp(); }
-
-    void sanitize() {
-        // If the area is empty, or the max amount is zero, then reset back to defaults
-        if (area.isEmpty() || isZero(maxAmount)) {
-            *this = StretchEffect{};
-        }
-    }
+  float getStretchHeightMultiplier() const {
+      return CONTENT_DISTANCE_STRETCHED / (1.f + abs(vectorY));
+  }
 };
 
 static_assert(std::is_trivially_copyable<StretchEffect>::value,
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 7bd0c6b..1b5f1ab 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -232,7 +232,8 @@
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
                      /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, currentTime, currentTime,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, currentTime, currentTime,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     return event;
 }
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 5270b8a..6ed9593 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -296,12 +296,13 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                             ui::Transform transform, float globalScaleFactor)
+                             ui::Transform transform, float globalScaleFactor, vec2 displaySize)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
         transform(transform),
         globalScaleFactor(globalScaleFactor),
+        displaySize(displaySize),
         deliveryTime(0),
         resolvedAction(0),
         resolvedFlags(0) {}
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index f3ef64b..45c5e24 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -215,6 +215,7 @@
     int32_t targetFlags;
     ui::Transform transform;
     float globalScaleFactor;
+    vec2 displaySize;
     // Both deliveryTime and timeoutTime are only populated when the entry is sent to the app,
     // and will be undefined before that.
     nsecs_t deliveryTime; // time when the event was actually delivered
@@ -227,7 +228,7 @@
     int32_t resolvedFlags;
 
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  ui::Transform transform, float globalScaleFactor);
+                  ui::Transform transform, float globalScaleFactor, vec2 displaySize);
 
     inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 27443b0..16cb7d7 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -339,14 +339,16 @@
             // values do not represent on-screen coordinates, so they should not have any window
             // transformations applied to them.
             return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, identityTransform,
-                                                   1.0f /*globalScaleFactor*/);
+                                                   1.0f /*globalScaleFactor*/,
+                                                   inputTarget.displaySize);
         }
     }
 
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
-                                               inputTarget.globalScaleFactor);
+                                               inputTarget.globalScaleFactor,
+                                               inputTarget.displaySize);
     }
 
     ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
@@ -397,7 +399,8 @@
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
-                                            firstPointerTransform, inputTarget.globalScaleFactor);
+                                            firstPointerTransform, inputTarget.globalScaleFactor,
+                                            inputTarget.displaySize);
     return dispatchEntry;
 }
 
@@ -2402,6 +2405,8 @@
         inputTarget.inputChannel = inputChannel;
         inputTarget.flags = targetFlags;
         inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
+        inputTarget.displaySize =
+                vec2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
         inputTargets.push_back(inputTarget);
         it = inputTargets.end() - 1;
     }
@@ -3107,6 +3112,8 @@
                                                      motionEntry.xPrecision, motionEntry.yPrecision,
                                                      motionEntry.xCursorPosition,
                                                      motionEntry.yCursorPosition,
+                                                     dispatchEntry->displaySize.x,
+                                                     dispatchEntry->displaySize.y,
                                                      motionEntry.downTime, motionEntry.eventTime,
                                                      motionEntry.pointerCount,
                                                      motionEntry.pointerProperties, usingCoords);
@@ -3819,7 +3826,8 @@
                              args->action, args->actionButton, args->flags, args->edgeFlags,
                              args->metaState, args->buttonState, args->classification, transform,
                              args->xPrecision, args->yPrecision, args->xCursorPosition,
-                             args->yCursorPosition, args->downTime, args->eventTime,
+                             args->yCursorPosition, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                             AMOTION_EVENT_INVALID_DISPLAY_SIZE, args->downTime, args->eventTime,
                              args->pointerCount, args->pointerProperties, args->pointerCoords);
 
             policyFlags |= POLICY_FLAG_FILTERED;
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index debf805..2543852 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -100,6 +100,9 @@
     // (ignored for KeyEvents)
     float globalScaleFactor = 1.0f;
 
+    // Display-size in its natural rotation. Used for compatibility transform of raw coordinates.
+    vec2 displaySize = {AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE};
+
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
     BitSet32 pointerIds;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 31d6900..9687c83 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -526,7 +526,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      /*action*/ -1, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -539,6 +540,7 @@
                              (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                      ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties,
                      pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -551,6 +553,7 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                      ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties,
                      pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -564,6 +567,7 @@
                              (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                      ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties,
                      pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -576,6 +580,7 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
                      ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties,
                      pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -587,7 +592,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -597,7 +603,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -609,7 +616,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -620,7 +628,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -633,7 +642,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
+                     AMOTION_EVENT_INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -1237,8 +1247,8 @@
                          mAction, mActionButton, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
-                         mRawYCursorPosition, mEventTime, mEventTime, mPointers.size(),
-                         pointerProperties.data(), pointerCoords.data());
+                         mRawYCursorPosition, mDisplayWidth, mDisplayHeight, mEventTime, mEventTime,
+                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
 
         return event;
     }
@@ -1252,6 +1262,8 @@
     int32_t mButtonState{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    int32_t mDisplayWidth{AMOTION_EVENT_INVALID_DISPLAY_SIZE};
+    int32_t mDisplayHeight{AMOTION_EVENT_INVALID_DISPLAY_SIZE};
 
     std::vector<PointerBuilder> mPointers;
 };
@@ -2029,6 +2041,19 @@
                                      expectedDisplayId, expectedFlags);
     }
 
+    MotionEvent* consumeMotion() {
+        InputEvent* event = mInputReceiver->consume();
+        if (!event) {
+            ADD_FAILURE() << "No event was produced";
+            return nullptr;
+        }
+        if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
+            ADD_FAILURE() << "Received event of type " << event->getType() << " instead of motion";
+            return nullptr;
+        }
+        return static_cast<MotionEvent*>(event);
+    }
+
     void assertNoEvents() { mInputReceiver->assertNoEvents(); }
 
 private:
@@ -2115,6 +2140,27 @@
     mFakePolicy->assertNotifyMonitorResponsiveWasCalled();
 }
 
+// Tests for gesture monitors
+TEST_F(InputDispatcherTest, GestureMonitor_NoWindowTransform) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    window->setWindowOffset(20, 40);
+    window->setWindowTransform(0, 1, -1, 0);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
+                                                      true /*isGestureMonitor*/);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    MotionEvent* event = monitor.consumeMotion();
+    // Even though window has transform, gesture monitor must not.
+    ASSERT_EQ(ui::Transform(), event->getTransform());
+}
+
 TEST_F(InputDispatcherTest, TestMoveEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index d8e8b52..5d6f8c7 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -132,8 +132,6 @@
 }
 
 void SensorDevice::initializeSensorList() {
-    float minPowerMa = 0.001; // 1 microAmp
-
     checkReturn(mSensors->getSensorsList(
             [&](const auto &list) {
                 const size_t count = list.size();
@@ -151,13 +149,18 @@
                         // Don't crash in this case since CTS will verify that devices don't go to
                         // production with a resolution of 0.
                         if (sensor.resolution != 0) {
-                            double promotedResolution = sensor.resolution;
-                            double promotedMaxRange = sensor.maxRange;
-                            if (fmod(promotedMaxRange, promotedResolution) != 0) {
-                                ALOGW("%s's max range %f is not a multiple of the resolution %f",
-                                        sensor.name, sensor.maxRange, sensor.resolution);
-                                SensorDeviceUtils::quantizeValue(
-                                        &sensor.maxRange, promotedResolution);
+                            float quantizedRange = sensor.maxRange;
+                            SensorDeviceUtils::quantizeValue(
+                                    &quantizedRange, sensor.resolution, /*factor=*/ 1);
+                            // Only rewrite maxRange if the requantization produced a "significant"
+                            // change, which is fairly arbitrarily defined as resolution / 8.
+                            // Smaller deltas are permitted, as they may simply be due to floating
+                            // point representation error, etc.
+                            if (fabsf(sensor.maxRange - quantizedRange) > sensor.resolution / 8) {
+                                ALOGW("%s's max range %.12f is not a multiple of the resolution "
+                                      "%.12f - updated to %.12f", sensor.name, sensor.maxRange,
+                                      sensor.resolution, quantizedRange);
+                                sensor.maxRange = quantizedRange;
                             }
                         } else {
                             // Don't crash here or the device will go into a crashloop.
@@ -166,10 +169,11 @@
                     }
 
                     // Sanity check and clamp power if it is 0 (or close)
-                    if (sensor.power < minPowerMa) {
-                        ALOGI("Reported power %f not deemed sane, clamping to %f",
-                              sensor.power, minPowerMa);
-                        sensor.power = minPowerMa;
+                    constexpr float MIN_POWER_MA = 0.001; // 1 microAmp
+                    if (sensor.power < MIN_POWER_MA) {
+                        ALOGI("%s's reported power %f invalid, clamped to %f",
+                              sensor.name, sensor.power, MIN_POWER_MA);
+                        sensor.power = MIN_POWER_MA;
                     }
                     mSensorList.push_back(sensor);
 
diff --git a/services/sensorservice/SensorDeviceUtils.h b/services/sensorservice/SensorDeviceUtils.h
index 1309971..255f7e1 100644
--- a/services/sensorservice/SensorDeviceUtils.h
+++ b/services/sensorservice/SensorDeviceUtils.h
@@ -32,16 +32,15 @@
 namespace android {
 namespace SensorDeviceUtils {
 
-// Quantizes a single value using a sensor's resolution.
-inline void quantizeValue(float *value, double resolution) {
+// Quantizes a single value to (a fractional factor of) a sensor's resolution. Typically we
+// increase the value of the sensor's nominal resolution to ensure that sensor accuracy
+// improvements, like runtime calibration, are not masked during requantization.
+inline void quantizeValue(float *value, double resolution, double factor = 0.125) {
     if (resolution == 0) {
         return;
     }
 
-    // Increase the value of the sensor's nominal resolution to ensure that
-    // sensor accuracy improvements, like runtime calibration, are not masked
-    // during requantization.
-    double incRes = 0.125 * resolution;
+    double incRes = factor * resolution;
     *value = round(static_cast<double>(*value) / incRes) * incRes;
 }
 
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 9885352..f20bfe1 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -25,7 +25,7 @@
     name: "libsurfaceflinger_defaults",
     defaults: [
         "surfaceflinger_defaults",
-        "skia_deps",
+        "skia_renderengine_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 7a5b20d..24b3599 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -284,6 +284,17 @@
     return true;
 }
 
+bool BufferStateLayer::setBufferCrop(const Rect& bufferCrop) {
+    if (mCurrentState.bufferCrop == bufferCrop) return false;
+
+    mCurrentState.sequence++;
+    mCurrentState.bufferCrop = bufferCrop;
+
+    mCurrentState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix,
                                  bool allowNonRectPreservingTransforms) {
     if (mCurrentState.transform.dsdx() == matrix.dsdx &&
@@ -809,10 +820,15 @@
 }
 
 Rect BufferStateLayer::computeBufferCrop(const State& s) {
-    if (s.buffer) {
+    if (s.buffer && !s.bufferCrop.isEmpty()) {
+        Rect bufferCrop;
+        s.buffer->getBuffer()->getBounds().intersect(s.bufferCrop, &bufferCrop);
+        return bufferCrop;
+    } else if (s.buffer) {
         return s.buffer->getBuffer()->getBounds();
+    } else {
+        return s.bufferCrop;
     }
-    return Rect::INVALID_RECT;
 }
 
 sp<Layer> BufferStateLayer::createClone() {
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index af4fcae..a273230 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -51,9 +51,6 @@
         return flags;
     }
 
-    uint32_t getActiveWidth(const Layer::State& s) const override { return s.width; }
-    uint32_t getActiveHeight(const Layer::State& s) const override { return s.height; }
-    ui::Transform getActiveTransform(const Layer::State& s) const override { return s.transform; }
     Region getActiveTransparentRegion(const Layer::State& s) const override {
         return s.transparentRegionHint;
     }
@@ -88,6 +85,8 @@
     FloatRect computeSourceBounds(const FloatRect& parentBounds) const override;
     void setAutoRefresh(bool autoRefresh) override;
 
+    bool setBufferCrop(const Rect& bufferCrop) override;
+
     // -----------------------------------------------------------------------
 
     // -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index aa049a8..fd22aa3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -57,11 +57,15 @@
     void getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
                       sp<GraphicBuffer>* outBuffer);
 
+    // Special caching slot for the layer caching feature.
+    static const constexpr size_t FLATTENER_CACHING_SLOT = BufferQueue::NUM_BUFFER_SLOTS;
+
 private:
     // an array where the index corresponds to a slot and the value corresponds to a (counter,
     // buffer) pair. "counter" is a unique value that indicates the last time this slot was updated
     // or used and allows us to keep track of the least-recently used buffer.
-    wp<GraphicBuffer> mBuffers[BufferQueue::NUM_BUFFER_SLOTS];
+    static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1;
+    wp<GraphicBuffer> mBuffers[kMaxLayerBufferCount];
 };
 
 } // namespace compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 356965c..b98043b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -102,7 +102,7 @@
         // behind the OutputLayer represented by this CompositionState and will
         // be visible through it. Unowned - the OutputLayer's lifetime will
         // outlast this.)
-        OutputLayer* peekThroughLayer = nullptr;
+        compositionengine::OutputLayer* peekThroughLayer = nullptr;
     } overrideInfo;
 
     /*
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index a6c4eaf..06f26eb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -60,7 +60,6 @@
     void addLayer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
 
     std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; }
-    NonBufferHash getFingerprint() const { return mFingerprint; }
     size_t getLayerCount() const { return mLayers.size(); }
     const Layer& getFirstLayer() const { return mLayers[0]; }
     const Rect& getBounds() const { return mBounds; }
@@ -121,7 +120,7 @@
     void addHolePunchLayerIfFeasible(const CachedSet&, bool isFirstLayer);
 
     // Retrieve the layer that will be drawn behind this one.
-    OutputLayer* getHolePunchLayer() const;
+    compositionengine::OutputLayer* getHolePunchLayer() const;
 
 private:
     CachedSet() = default;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 942592a..b09f1d1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -49,6 +49,7 @@
                           const OutputCompositionState& outputState);
 
     void dump(std::string& result) const;
+    void dumpLayers(std::string& result) const;
 
 private:
     size_t calculateDisplayCost(const std::vector<const LayerState*>& layers) const;
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
index 2d9f01b..b1ee3fb 100644
--- a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -36,7 +36,8 @@
             lhs.sourceDataspace == rhs.sourceDataspace &&
             lhs.colorTransform == rhs.colorTransform &&
             lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow &&
-            lhs.backgroundBlurRadius == rhs.backgroundBlurRadius;
+            lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
+            lhs.stretchEffect == rhs.stretchEffect;
 }
 
 inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
index 0cc2c6e..5565396 100644
--- a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -101,9 +101,9 @@
 }
 
 void dumpVal(std::string& out, const char* name, const StretchEffect& effect) {
-    StringAppendF(&out, "%s={ area=[%f, %f, %f, %f], vec=(%f, %f), max=%f } ", name,
-                  effect.area.left, effect.area.top, effect.area.right, effect.area.bottom,
-                  effect.vectorX, effect.vectorY, effect.maxAmount);
+    StringAppendF(&out, "%s={ width =%f, height = %f, vec=(%f, %f), max=(%f, %f) } ", name,
+                  effect.width, effect.height,
+                  effect.vectorX, effect.vectorY, effect.maxAmountX, effect.maxAmountY);
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index cedc333..f95382d 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -36,7 +36,7 @@
                                   sp<GraphicBuffer>* outBuffer) {
     // default is 0
     if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 ||
-        slot >= BufferQueue::NUM_BUFFER_SLOTS) {
+        slot >= static_cast<int32_t>(kMaxLayerBufferCount)) {
         *outSlot = 0;
     } else {
         *outSlot = static_cast<uint32_t>(slot);
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index c809e1a..297e687 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -707,7 +707,7 @@
 
     editState().earliestPresentTime = refreshArgs.earliestPresentTime;
 
-    OutputLayer* peekThroughLayer = nullptr;
+    compositionengine::OutputLayer* peekThroughLayer = nullptr;
     sp<GraphicBuffer> previousOverride = nullptr;
     bool includeGeometry = refreshArgs.updatingGeometryThisFrame;
     uint32_t z = 0;
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 7f5c01c..01da070 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -18,6 +18,7 @@
 #include <compositionengine/DisplayColorProfile.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/Output.h>
+#include <compositionengine/impl/HwcBufferCache.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
@@ -548,9 +549,11 @@
 
     sp<GraphicBuffer> buffer = outputIndependentState.buffer;
     sp<Fence> acquireFence = outputIndependentState.acquireFence;
+    int slot = outputIndependentState.bufferSlot;
     if (getState().overrideInfo.buffer != nullptr) {
         buffer = getState().overrideInfo.buffer->getBuffer();
         acquireFence = getState().overrideInfo.acquireFence;
+        slot = HwcBufferCache::FLATTENER_CACHING_SLOT;
     }
 
     ALOGV("Writing buffer %p", buffer.get());
@@ -559,8 +562,7 @@
     sp<GraphicBuffer> hwcBuffer;
     // We need access to the output-dependent state for the buffer cache there,
     // though otherwise the buffer is not output-dependent.
-    editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot, buffer,
-                                                 &hwcSlot, &hwcBuffer);
+    editState().hwc->hwcBufferCache.getHwcBuffer(slot, buffer, &hwcSlot, &hwcBuffer);
 
     if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence);
         error != hal::Error::NONE) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 4cf83d9..67854cf 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -306,7 +306,7 @@
     }
 }
 
-OutputLayer* CachedSet::getHolePunchLayer() const {
+compositionengine::OutputLayer* CachedSet::getHolePunchLayer() const {
     return mHolePunchLayer ? mHolePunchLayer->getOutputLayer() : nullptr;
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index b4e7610..a63f21f 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -45,7 +45,10 @@
     }
 
     for (size_t i = 0; i < incomingLayers.size(); i++) {
-        if (incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
+        // Checking the IDs here is very strict, but we do this as otherwise we may mistakenly try
+        // to access destroyed OutputLayers later on.
+        if (incomingLayers[i]->getId() != existingLayers[i]->getId() ||
+            incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
             return false;
         }
     }
@@ -96,6 +99,14 @@
     mNewCachedSet->render(renderEngine, outputState);
 }
 
+void Flattener::dumpLayers(std::string& result) const {
+    result.append("  Current layers:");
+    for (const CachedSet& layer : mLayers) {
+        result.append("\n");
+        layer.dump(result);
+    }
+}
+
 void Flattener::dump(std::string& result) const {
     const auto now = std::chrono::steady_clock::now();
 
@@ -140,11 +151,7 @@
     base::StringAppendF(&result, "\n  Current hash %016zx, last update %sago\n\n", mCurrentGeometry,
                         durationString(lastUpdate).c_str());
 
-    result.append("  Current layers:");
-    for (const CachedSet& layer : mLayers) {
-        result.append("\n");
-        layer.dump(result);
-    }
+    dumpLayers(result);
 }
 
 size_t Flattener::calculateDisplayCost(const std::vector<const LayerState*>& layers) const {
@@ -232,7 +239,8 @@
     auto currentLayerIter = mLayers.begin();
     auto incomingLayerIter = layers.begin();
     while (incomingLayerIter != layers.end()) {
-        if (mNewCachedSet && mNewCachedSet->getFingerprint() == (*incomingLayerIter)->getHash()) {
+        if (mNewCachedSet &&
+            mNewCachedSet->getFirstLayer().getState()->getId() == (*incomingLayerIter)->getId()) {
             if (mNewCachedSet->hasBufferUpdate()) {
                 ALOGV("[%s] Dropping new cached set", __func__);
                 ++mInvalidatedCachedSetAges[0];
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 7d2bf06..7e85dca 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -233,6 +233,8 @@
             }
 
             mPredictor.listSimilarStacks(*plan, result);
+        } else if (command == "--layers" || command == "-l") {
+            mFlattener.dumpLayers(result);
         } else {
             base::StringAppendF(&result, "Unknown command '%s'\n\n", command.string());
             dumpUsage(result);
@@ -268,6 +270,9 @@
 
     result.append("[--similar|-s] <plan>\n");
     result.append("  Prints the example layer names for similar stacks matching <plan>\n");
+
+    result.append("[--layers|-l]\n");
+    result.append("  Prints the current layers\n");
 }
 
 } // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index fb8ffce..3adfe40 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <compositionengine/impl/HwcBufferCache.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/mock/CompositionEngine.h>
@@ -702,6 +703,7 @@
     static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(72);
     static constexpr int kSupportedPerFrameMetadata = 101;
     static constexpr int kExpectedHwcSlot = 0;
+    static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT;
     static constexpr bool kLayerGenericMetadata1Mandatory = true;
     static constexpr bool kLayerGenericMetadata2Mandatory = true;
 
@@ -824,10 +826,11 @@
         EXPECT_CALL(*mHwcLayer, setSidebandStream(kSidebandStreamHandle));
     }
 
-    void expectSetHdrMetadataAndBufferCalls(sp<GraphicBuffer> buffer = kBuffer,
+    void expectSetHdrMetadataAndBufferCalls(uint32_t hwcSlot = kExpectedHwcSlot,
+                                            sp<GraphicBuffer> buffer = kBuffer,
                                             sp<Fence> fence = kFence) {
         EXPECT_CALL(*mHwcLayer, setPerFrameMetadata(kSupportedPerFrameMetadata, kHdrMetadata));
-        EXPECT_CALL(*mHwcLayer, setBuffer(kExpectedHwcSlot, buffer, fence));
+        EXPECT_CALL(*mHwcLayer, setBuffer(hwcSlot, buffer, fence));
     }
 
     void expectGenericLayerMetadataCalls() {
@@ -1060,15 +1063,75 @@
                               kOverrideBlendMode, kOverrideAlpha);
     expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
                               kOverrideSurfaceDamage);
-    expectSetHdrMetadataAndBufferCalls(kOverrideBuffer, kOverrideFence);
+    expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
-
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
 
+TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) {
+    auto peekThroughLayerFE = sp<compositionengine::mock::LayerFE>::make();
+    OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE};
+
+    mOutputLayer.mState.overrideInfo.peekThroughLayer = &peekThroughLayer;
+
+    expectGeometryCommonCalls(kDisplayFrame, kSourceCrop, kBufferTransform,
+                              Hwc2::IComposerClient::BlendMode::PREMULTIPLIED);
+    expectPerFrameCommonCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, isPeekingThroughSetsOverride) {
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls();
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ true);
+    EXPECT_TRUE(mOutputLayer.getState().hwc->stateOverridden);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) {
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ true, /*isPeekingThrough*/
+                                 false);
+    EXPECT_TRUE(mOutputLayer.getState().hwc->stateOverridden);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersForceClientComposition) {
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
+    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/
+                                 false);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersPeekingThroughAllowsDeviceComposition) {
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls();
+    expectSetHdrMetadataAndBufferCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+
+    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/
+                                 true);
+    EXPECT_EQ(Hwc2::IComposerClient::Composition::DEVICE,
+              mOutputLayer.getState().hwc->hwcCompositionType);
+}
+
 /*
  * OutputLayer::writeCursorPositionToHWC()
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 7b71957..27980a0 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -879,6 +879,64 @@
     mOutput->writeCompositionState(args);
 }
 
+TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) {
+    renderengine::mock::RenderEngine renderEngine;
+    InjectedLayer layer0;
+    InjectedLayer layer1;
+    InjectedLayer layer2;
+    InjectedLayer layer3;
+
+    InSequence seq;
+    EXPECT_CALL(*layer0.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+
+    uint32_t z = 0;
+    EXPECT_CALL(*layer0.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+
+    // After calling planComposition (which clears overrideInfo), this test sets
+    // layer3 to be the peekThroughLayer for layer1 and layer2. As a result, it
+    // comes first, setting isPeekingThrough to true and zIsOverridden to true
+    // for it and the following layers.
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
+                                /*zIsOverridden*/ true, /*isPeekingThrough*/
+                                true));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
+                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, z++,
+                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false));
+
+    injectOutputLayer(layer0);
+    injectOutputLayer(layer1);
+    injectOutputLayer(layer2);
+    injectOutputLayer(layer3);
+
+    mOutput->editState().isEnabled = true;
+
+    CompositionRefreshArgs args;
+    args.updatingGeometryThisFrame = true;
+    args.devOptForceClientComposition = false;
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+
+    std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+            renderengine::ExternalTexture>(new GraphicBuffer(), renderEngine,
+                                           renderengine::ExternalTexture::Usage::READABLE |
+                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+    layer1.outputLayerState.overrideInfo.buffer = buffer;
+    layer2.outputLayerState.overrideInfo.buffer = buffer;
+    layer1.outputLayerState.overrideInfo.peekThroughLayer = layer3.outputLayer;
+    layer2.outputLayerState.overrideInfo.peekThroughLayer = layer3.outputLayer;
+
+    mOutput->writeCompositionState(args);
+}
+
 /*
  * Output::prepareFrame()
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 8884711..a39331c 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -19,8 +19,12 @@
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
+#include <gmock/gmock-actions.h>
 #include <gtest/gtest.h>
+#include <renderengine/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
+#include <ui/GraphicTypes.h>
+#include <memory>
 
 namespace android::compositionengine {
 using namespace std::chrono_literals;
@@ -105,7 +109,6 @@
 }
 
 void expectEqual(const CachedSet& cachedSet, const CachedSet::Layer& layer) {
-    EXPECT_EQ(layer.getHash(), cachedSet.getFingerprint());
     EXPECT_EQ(layer.getLastUpdate(), cachedSet.getLastUpdate());
     EXPECT_EQ(layer.getDisplayFrame(), cachedSet.getBounds());
     EXPECT_TRUE(layer.getVisibleRegion().hasSameRects(cachedSet.getVisibleRegion()));
@@ -154,7 +157,6 @@
     CachedSet cachedSet(layer1);
     cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
 
-    EXPECT_EQ(layer1.getHash(), cachedSet.getFingerprint());
     EXPECT_EQ(kStartTime, cachedSet.getLastUpdate());
     EXPECT_EQ(Rect(0, 0, 2, 2), cachedSet.getBounds());
     Region expectedRegion;
@@ -243,7 +245,6 @@
     cachedSet1.addLayer(layer3.getState(), kStartTime + 10ms);
     cachedSet1.append(cachedSet2);
 
-    EXPECT_EQ(layer1.getHash(), cachedSet1.getFingerprint());
     EXPECT_EQ(kStartTime, cachedSet1.getLastUpdate());
     EXPECT_EQ(Rect(0, 0, 3, 3), cachedSet1.getBounds());
     Region expectedRegion;
@@ -380,5 +381,191 @@
     cachedSet.append(CachedSet(layer3));
 }
 
+TEST_F(CachedSetTest, holePunch_requiresBuffer) {
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE1 = mTestLayers[0]->layerFE;
+
+    CachedSet cachedSet(layer1);
+    EXPECT_CALL(*layerFE1, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, holePunch_requiresRoundedCorners) {
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+
+    CachedSet cachedSet(layer1);
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, holePunch_requiresSingleLayer) {
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.append(layer2);
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, requiresHolePunch) {
+    CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+
+    CachedSet cachedSet(layer);
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_TRUE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, holePunch_requiresDeviceComposition) {
+    CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.forceClientComposition = true;
+
+    CachedSet cachedSet(layer);
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, addHolePunch_requiresOverlap) {
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, true);
+
+    ASSERT_EQ(nullptr, cachedSet.getHolePunchLayer());
+}
+
+TEST_F(CachedSetTest, addHolePunch_requiresOpaque) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    mTestLayers[0]->layerFECompositionState.isOpaque = false;
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, false);
+
+    ASSERT_EQ(nullptr, cachedSet.getHolePunchLayer());
+}
+
+TEST_F(CachedSetTest, addHolePunch_opaque) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    mTestLayers[0]->layerFECompositionState.isOpaque = true;
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, false);
+
+    ASSERT_EQ(&mTestLayers[2]->outputLayer, cachedSet.getHolePunchLayer());
+}
+
+TEST_F(CachedSetTest, addHolePunch_firstLayer) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    mTestLayers[0]->layerFECompositionState.isOpaque = false;
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, true);
+
+    ASSERT_EQ(&mTestLayers[2]->outputLayer, cachedSet.getHolePunchLayer());
+}
+
+TEST_F(CachedSetTest, addHolePunch) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE1 = mTestLayers[0]->layerFE;
+
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE2 = mTestLayers[1]->layerFE;
+
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE3 = mTestLayers[2]->layerFE;
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, true);
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
+    clientCompList1.push_back({});
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
+    clientCompList2.push_back({});
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList3;
+    clientCompList3.push_back({});
+
+    clientCompList3[0].source.buffer.buffer = std::make_shared<
+            renderengine::ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
+                                           renderengine::ExternalTexture::READABLE);
+
+    EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
+    EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
+    EXPECT_CALL(*layerFE3, prepareClientCompositionList(_)).WillOnce(Return(clientCompList3));
+
+    const auto drawLayers = [&](const renderengine::DisplaySettings&,
+                                const std::vector<const renderengine::LayerSettings*>& layers,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&, base::unique_fd*) -> size_t {
+        // If the highlight layer is enabled, it will increase the size by 1.
+        // We're interested in the third layer either way.
+        EXPECT_GE(layers.size(), 3u);
+        const auto* holePunchSettings = layers[2];
+        EXPECT_EQ(nullptr, holePunchSettings->source.buffer.buffer);
+        EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchSettings->source.solidColor);
+        EXPECT_TRUE(holePunchSettings->disableBlending);
+        EXPECT_EQ(0.0f, holePunchSettings->alpha);
+
+        return NO_ERROR;
+    };
+
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    cachedSet.render(mRenderEngine, mOutputState);
+}
+
+TEST_F(CachedSetTest, decompose_removesHolePunch) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet(layer1);
+    cachedSet.addLayer(layer2.getState(), kStartTime + 10ms);
+
+    cachedSet.addHolePunchLayerIfFeasible(layer3, true);
+
+    ASSERT_EQ(&mTestLayers[2]->outputLayer, cachedSet.getHolePunchLayer());
+
+    std::vector<CachedSet> decomposed = cachedSet.decompose();
+    EXPECT_EQ(2u, decomposed.size());
+    for (const auto& set : decomposed) {
+        EXPECT_EQ(nullptr, set.getHolePunchLayer());
+    }
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 373b895..71757f6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -22,6 +22,8 @@
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
 #include <gtest/gtest.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/LayerSettings.h>
 #include <renderengine/mock/RenderEngine.h>
 
 namespace android::compositionengine {
@@ -45,7 +47,7 @@
 
 class FlattenerTest : public testing::Test {
 public:
-    FlattenerTest() : mFlattener(std::make_unique<Flattener>(mPredictor)) {}
+    FlattenerTest() : mFlattener(std::make_unique<Flattener>(mPredictor, true)) {}
     void SetUp() override;
 
 protected:
@@ -528,5 +530,123 @@
     EXPECT_EQ(overrideBuffer4, overrideBuffer5);
 }
 
+// Tests for a PIP
+TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // 3 has a buffer update, so it will not be merged, but it has no round
+    // corners, so it is not a PIP.
+    mTime += 200ms;
+    layerState3->resetFramesSinceBufferUpdate();
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
+TEST_F(FlattenerTest, flattenLayers_pip) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+
+    EXPECT_CALL(*mTestLayers[2]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    std::vector<LayerFE::LayerSettings> clientCompositionList = {
+            LayerFE::LayerSettings{},
+    };
+    clientCompositionList[0].source.buffer.buffer = std::make_shared<
+            renderengine::ExternalTexture>(mTestLayers[2]->layerFECompositionState.buffer,
+                                           mRenderEngine,
+                                           renderengine::ExternalTexture::Usage::READABLE);
+    EXPECT_CALL(*mTestLayers[2]->layerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(clientCompositionList));
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // 3 has a buffer update, so it will not be merged, and it has round
+    // corners, so it is a PIP.
+    mTime += 200ms;
+    layerState3->resetFramesSinceBufferUpdate();
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    const auto* peekThroughLayer1 =
+            layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    const auto* peekThroughLayer2 =
+            layerState2->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    EXPECT_EQ(&mTestLayers[2]->outputLayer, peekThroughLayer1);
+    EXPECT_EQ(peekThroughLayer1, peekThroughLayer2);
+}
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index fd70988..1cbcf59 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -25,7 +25,6 @@
 #include "ComposerHal.h"
 
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
-#include <gui/BufferQueue.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/HidlTransportUtils.h>
 #include <log/log.h>
@@ -266,15 +265,15 @@
 Error Composer::createLayer(Display display, Layer* outLayer)
 {
     Error error = kDefaultError;
-    mClient->createLayer(display, BufferQueue::NUM_BUFFER_SLOTS,
-            [&](const auto& tmpError, const auto& tmpLayer) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
+    mClient->createLayer(display, kMaxLayerBufferCount,
+                         [&](const auto& tmpError, const auto& tmpLayer) {
+                             error = tmpError;
+                             if (error != Error::NONE) {
+                                 return;
+                             }
 
-                *outLayer = tmpLayer;
-            });
+                             *outLayer = tmpLayer;
+                         });
 
     return error;
 }
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index c756d65..0619b8c 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,6 +32,7 @@
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 #include <composer-command-buffer/2.4/ComposerCommandBuffer.h>
+#include <gui/BufferQueue.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/DisplayedFrameStats.h>
@@ -491,6 +492,11 @@
     // 64KiB minus a small space for metadata such as read/write pointers
     static constexpr size_t kWriterInitialSize =
         64 * 1024 / sizeof(uint32_t) - 16;
+    // Max number of buffers that may be cached for a given layer
+    // We obtain this number by:
+    // 1. Tightly coupling this cache to the max size of BufferQueue
+    // 2. Adding an additional slot for the layer caching feature in SurfaceFlinger (see: Planner.h)
+    static const constexpr uint32_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1;
     CommandWriter mWriter;
     CommandReader mReader;
 };
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 0033dbe..f19e2a7 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -730,9 +730,11 @@
 int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
     ATRACE_CALL();
     std::scoped_lock lock(mMutex);
+    while (mPredictions.size() >= kMaxTokens) {
+        mPredictions.erase(mPredictions.begin());
+    }
     const int64_t assignedToken = mCurrentToken++;
-    mPredictions[assignedToken] = {systemTime(), predictions};
-    flushTokens(systemTime());
+    mPredictions[assignedToken] = predictions;
     return assignedToken;
 }
 
@@ -740,23 +742,11 @@
     std::scoped_lock lock(mMutex);
     auto predictionsIterator = mPredictions.find(token);
     if (predictionsIterator != mPredictions.end()) {
-        return predictionsIterator->second.predictions;
+        return predictionsIterator->second;
     }
     return {};
 }
 
-void TokenManager::flushTokens(nsecs_t flushTime) {
-    for (auto it = mPredictions.begin(); it != mPredictions.end();) {
-        if (flushTime - it->second.timestamp >= kMaxRetentionTime) {
-            it = mPredictions.erase(it);
-        } else {
-            // Tokens are ordered by time. If i'th token is within the retention time, then the
-            // i+1'th token will also be within retention time.
-            break;
-        }
-    }
-}
-
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
                              JankClassificationThresholds thresholds)
       : mMaxDisplayFrames(kDefaultMaxDisplayFrames),
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 0563a53..42be55a 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -92,11 +92,6 @@
     bool operator!=(const TimelineItem& other) const { return !(*this == other); }
 };
 
-struct TokenManagerPrediction {
-    nsecs_t timestamp = 0;
-    TimelineItem predictions;
-};
-
 struct JankClassificationThresholds {
     // The various thresholds for App and SF. If the actual timestamp falls within the threshold
     // compared to prediction, we treat it as on time.
@@ -334,11 +329,10 @@
 
     void flushTokens(nsecs_t flushTime) REQUIRES(mMutex);
 
-    std::map<int64_t, TokenManagerPrediction> mPredictions GUARDED_BY(mMutex);
+    std::map<int64_t, TimelineItem> mPredictions GUARDED_BY(mMutex);
     int64_t mCurrentToken GUARDED_BY(mMutex);
     mutable std::mutex mMutex;
-    static constexpr nsecs_t kMaxRetentionTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(120ms).count();
+    static constexpr size_t kMaxTokens = 500;
 };
 
 class FrameTimeline : public android::frametimeline::FrameTimeline {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 4461420..21c9d74 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -632,8 +632,10 @@
     if (!targetSettings.disableBlurs) {
         layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
         layerSettings.blurRegions = getBlurRegions();
+        layerSettings.blurRegionTransform =
+                getActiveTransform(getDrawingState()).inverse().asMatrix4();
     }
-    layerSettings.stretchEffect = getDrawingState().stretchEffect;
+    layerSettings.stretchEffect = getStretchEffect();
     // Record the name of the layer for debugging further down the stack.
     layerSettings.name = getName();
     return layerSettings;
@@ -856,7 +858,7 @@
     const State& s(getDrawingState());
     State& c(getCurrentState());
 
-    if (getActiveGeometry(c) != getActiveGeometry(s)) {
+    if (c.width != s.width || c.height != s.height || !(c.transform == s.transform)) {
         // invalidate and recompute the visible regions if needed
         flags |= Layer::eVisibleRegion;
     }
@@ -931,20 +933,18 @@
 }
 
 bool Layer::setPosition(float x, float y) {
-    if (mCurrentState.requested_legacy.transform.tx() == x &&
-        mCurrentState.requested_legacy.transform.ty() == y)
-        return false;
+    if (mCurrentState.transform.tx() == x && mCurrentState.transform.ty() == y) return false;
     mCurrentState.sequence++;
 
     // We update the requested and active position simultaneously because
     // we want to apply the position portion of the transform matrix immediately,
     // but still delay scaling when resizing a SCALING_MODE_FREEZE layer.
-    mCurrentState.requested_legacy.transform.set(x, y);
+    mCurrentState.transform.set(x, y);
     // Here we directly update the active state
     // unlike other setters, because we store it within
     // the transform, but use different latching rules.
     // b/38182305
-    mCurrentState.active_legacy.transform.set(x, y);
+    mCurrentState.transform.set(x, y);
 
     mCurrentState.modified = true;
     setTransactionFlags(eTransactionNeeded);
@@ -1063,6 +1063,7 @@
     setDefaultBufferSize(mCurrentState.requested_legacy.w, mCurrentState.requested_legacy.h);
     return true;
 }
+
 bool Layer::setAlpha(float alpha) {
     if (mCurrentState.color.a == alpha) return false;
     mCurrentState.sequence++;
@@ -1141,8 +1142,7 @@
         return false;
     }
     mCurrentState.sequence++;
-    mCurrentState.requested_legacy.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx,
-                                                 matrix.dsdy);
+    mCurrentState.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
     mCurrentState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
@@ -1558,20 +1558,20 @@
     info.mVisibleRegion = getVisibleRegion(display);
     info.mSurfaceDamageRegion = surfaceDamageRegion;
     info.mLayerStack = getLayerStack();
-    info.mX = ds.active_legacy.transform.tx();
-    info.mY = ds.active_legacy.transform.ty();
+    info.mX = ds.transform.tx();
+    info.mY = ds.transform.ty();
     info.mZ = ds.z;
-    info.mWidth = ds.active_legacy.w;
-    info.mHeight = ds.active_legacy.h;
+    info.mWidth = ds.width;
+    info.mHeight = ds.height;
     info.mCrop = ds.crop;
     info.mColor = ds.color;
     info.mFlags = ds.flags;
     info.mPixelFormat = getPixelFormat();
     info.mDataSpace = static_cast<android_dataspace>(getDataSpace());
-    info.mMatrix[0][0] = ds.active_legacy.transform[0][0];
-    info.mMatrix[0][1] = ds.active_legacy.transform[0][1];
-    info.mMatrix[1][0] = ds.active_legacy.transform[1][0];
-    info.mMatrix[1][1] = ds.active_legacy.transform[1][1];
+    info.mMatrix[0][0] = ds.transform[0][0];
+    info.mMatrix[0][1] = ds.transform[0][1];
+    info.mMatrix[1][0] = ds.transform[1][0];
+    info.mMatrix[1][1] = ds.transform[1][1];
     {
         sp<const GraphicBuffer> buffer = getBuffer();
         if (buffer != 0) {
@@ -2447,6 +2447,8 @@
     ui::Transform toPhysicalDisplay;
     if (display) {
         toPhysicalDisplay = display->getTransform();
+        info.displayWidth = display->getWidth();
+        info.displayHeight = display->getHeight();
     }
     fillInputFrameInfo(info, toPhysicalDisplay);
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 688a2c3..284adbd 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -274,6 +274,8 @@
 
         // Stretch effect to apply to this layer
         StretchEffect stretchEffect;
+
+        Rect bufferCrop;
     };
 
     /*
@@ -482,12 +484,9 @@
     // to avoid grabbing the lock again to avoid deadlock
     virtual bool isCreatedFromMainThread() const { return false; }
 
-    virtual Geometry getActiveGeometry(const Layer::State& s) const { return s.active_legacy; }
-    virtual uint32_t getActiveWidth(const Layer::State& s) const { return s.active_legacy.w; }
-    virtual uint32_t getActiveHeight(const Layer::State& s) const { return s.active_legacy.h; }
-    virtual ui::Transform getActiveTransform(const Layer::State& s) const {
-        return s.active_legacy.transform;
-    }
+    uint32_t getActiveWidth(const Layer::State& s) const { return s.width; }
+    uint32_t getActiveHeight(const Layer::State& s) const { return s.height; }
+    ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; }
     virtual Region getActiveTransparentRegion(const Layer::State& s) const {
         return s.activeTransparentRegion_legacy;
     }
@@ -885,6 +884,7 @@
     bool setStretchEffect(const StretchEffect& effect);
     StretchEffect getStretchEffect() const;
 
+    virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; }
     virtual std::atomic<int32_t>* getPendingBufferCounter() { return nullptr; }
     virtual std::string getPendingBufferCounterName() { return ""; }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b048682..beda834 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1741,12 +1741,6 @@
 }
 
 void SurfaceFlinger::onMessageReceived(int32_t what, int64_t vsyncId, nsecs_t expectedVSyncTime) {
-    const auto vsyncIn = [&] {
-        if (!ATRACE_ENABLED()) return 0.f;
-        return (expectedVSyncTime - systemTime()) / 1e6f;
-    }();
-
-    ATRACE_FORMAT("onMessageReceived %" PRId64 " vsyncIn %.2fms", vsyncId, vsyncIn);
     switch (what) {
         case MessageQueue::INVALIDATE: {
             onMessageInvalidate(vsyncId, expectedVSyncTime);
@@ -1760,8 +1754,6 @@
 }
 
 void SurfaceFlinger::onMessageInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTime) {
-    ATRACE_CALL();
-
     const nsecs_t frameStart = systemTime();
     // calculate the expected present time once and use the cached
     // value throughout this frame to make sure all layers are
@@ -1776,6 +1768,13 @@
     const nsecs_t lastScheduledPresentTime = mScheduledPresentTime;
     mScheduledPresentTime = expectedVSyncTime;
 
+    const auto vsyncIn = [&] {
+        if (!ATRACE_ENABLED()) return 0.f;
+        return (mExpectedPresentTime - systemTime()) / 1e6f;
+    }();
+    ATRACE_FORMAT("onMessageInvalidate %" PRId64 " vsyncIn %.2fms%s", vsyncId, vsyncIn,
+                  mExpectedPresentTime == expectedVSyncTime ? "" : " (adjusted)");
+
     // When Backpressure propagation is enabled we want to give a small grace period
     // for the present fence to fire instead of just giving up on this frame to handle cases
     // where present fence is just about to get signaled.
@@ -3105,7 +3104,7 @@
 
 void SurfaceFlinger::commitTransaction() {
     commitTransactionLocked();
-    signalSynchronousTransactions();
+    signalSynchronousTransactions(CountDownLatch::eSyncTransaction);
     mAnimTransactionPending = false;
 }
 
@@ -3527,7 +3526,9 @@
     // Generate a CountDownLatch pending state if this is a synchronous transaction.
     if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {
         state.transactionCommittedSignal = std::make_shared<CountDownLatch>(
-                (state.inputWindowCommands.syncInputWindows ? 2 : 1));
+                (state.inputWindowCommands.syncInputWindows
+                         ? (CountDownLatch::eSyncInputWindows | CountDownLatch::eSyncTransaction)
+                         : CountDownLatch::eSyncTransaction));
     }
 
     mTransactionQueue.emplace(state);
@@ -3552,10 +3553,10 @@
     }
 }
 
-void SurfaceFlinger::signalSynchronousTransactions() {
+void SurfaceFlinger::signalSynchronousTransactions(const uint32_t flag) {
     for (auto it = mTransactionCommittedSignals.begin();
          it != mTransactionCommittedSignals.end();) {
-        if ((*it)->countDown() == 0) {
+        if ((*it)->countDown(flag)) {
             it = mTransactionCommittedSignals.erase(it);
         } else {
             it++;
@@ -4033,6 +4034,11 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eBufferCropChanged) {
+        if (layer->setBufferCrop(s.bufferCrop)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     // This has to happen after we reparent children because when we reparent to null we remove
     // child layers from current state and remove its relative z. If the children are reparented in
     // the same transaction, then we have to make sure we reparent the children first so we do not
@@ -4982,6 +4988,13 @@
         result.append("\n");
     }
 
+    {
+        DumpArgs plannerArgs;
+        plannerArgs.add(); // first argument is ignored
+        plannerArgs.add(String16("--layers"));
+        dumpPlannerInfo(plannerArgs, result);
+    }
+
     /*
      * Dump HWComposer state
      */
@@ -6163,7 +6176,7 @@
 
 void SurfaceFlinger::setInputWindowsFinished() {
     Mutex::Autolock _l(mStateLock);
-    signalSynchronousTransactions();
+    signalSynchronousTransactions(CountDownLatch::eSyncInputWindows);
 }
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index cf1a545..4bbdd48 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -467,24 +467,31 @@
 
     class CountDownLatch {
     public:
-        explicit CountDownLatch(int32_t count) : mCount(count) {}
+        enum {
+            eSyncTransaction = 1 << 0,
+            eSyncInputWindows = 1 << 1,
+        };
+        explicit CountDownLatch(uint32_t flags) : mFlags(flags) {}
 
-        int32_t countDown() {
+        // True if there is no waiting condition after count down.
+        bool countDown(uint32_t flag) {
             std::unique_lock<std::mutex> lock(mMutex);
-            if (mCount == 0) {
-                return 0;
+            if (mFlags == 0) {
+                return true;
             }
-            if (--mCount == 0) {
+            mFlags &= ~flag;
+            if (mFlags == 0) {
                 mCountDownComplete.notify_all();
+                return true;
             }
-            return mCount;
+            return false;
         }
 
         // Return true if triggered.
         bool wait_until(const std::chrono::seconds& timeout) const {
             std::unique_lock<std::mutex> lock(mMutex);
             const auto untilTime = std::chrono::system_clock::now() + timeout;
-            while (mCount != 0) {
+            while (mFlags != 0) {
                 // Conditional variables can be woken up sporadically, so we check count
                 // to verify the wakeup was triggered by |countDown|.
                 if (std::cv_status::timeout == mCountDownComplete.wait_until(lock, untilTime)) {
@@ -495,7 +502,7 @@
         }
 
     private:
-        int32_t mCount;
+        uint32_t mFlags;
         mutable std::condition_variable mCountDownComplete;
         mutable std::mutex mMutex;
     };
@@ -1124,7 +1131,7 @@
     // Add transaction to the Transaction Queue
     void queueTransaction(TransactionState& state) EXCLUDES(mQueueLock);
     void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal);
-    void signalSynchronousTransactions();
+    void signalSynchronousTransactions(const uint32_t flag);
 
     /*
      * Generic Layer Metadata
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 113f463..c5f1598 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -130,8 +130,8 @@
     transaction->set_animation(layerFlags & BnSurfaceComposer::eAnimation);
 
     const int32_t layerId(getLayerId(layer));
-    addPositionLocked(transaction, layerId, layer->mCurrentState.active_legacy.transform.tx(),
-                      layer->mCurrentState.active_legacy.transform.ty());
+    addPositionLocked(transaction, layerId, layer->mCurrentState.transform.tx(),
+                      layer->mCurrentState.transform.ty());
     addDepthLocked(transaction, layerId, layer->mCurrentState.z);
     addAlphaLocked(transaction, layerId, layer->mCurrentState.color.a);
     addTransparentRegionLocked(transaction, layerId,
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 3d82afa..10d58a6 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -427,8 +427,8 @@
     return true;
 }
 
-static int32_t clampToSmallestBucket(Fps fps, size_t bucketWidth) {
-    return (fps.getIntValue() / bucketWidth) * bucketWidth;
+static int32_t clampToNearestBucket(Fps fps, size_t bucketWidth) {
+    return std::round(fps.getValue() / bucketWidth) * bucketWidth;
 }
 
 void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
@@ -441,10 +441,10 @@
     TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
     std::deque<TimeRecord>& timeRecords = layerRecord.timeRecords;
     const int32_t refreshRateBucket =
-            clampToSmallestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH);
+            clampToNearestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH);
     const int32_t renderRateBucket =
-            clampToSmallestBucket(renderRate ? *renderRate : displayRefreshRate,
-                                  RENDER_RATE_BUCKET_WIDTH);
+            clampToNearestBucket(renderRate ? *renderRate : displayRefreshRate,
+                                 RENDER_RATE_BUCKET_WIDTH);
     while (!timeRecords.empty()) {
         if (!recordReadyLocked(layerId, &timeRecords[0])) break;
         ALOGV("[%d]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerId,
@@ -799,10 +799,10 @@
     static const std::string kDefaultLayerName = "none";
 
     const int32_t refreshRateBucket =
-            clampToSmallestBucket(info.refreshRate, REFRESH_RATE_BUCKET_WIDTH);
+            clampToNearestBucket(info.refreshRate, REFRESH_RATE_BUCKET_WIDTH);
     const int32_t renderRateBucket =
-            clampToSmallestBucket(info.renderRate ? *info.renderRate : info.refreshRate,
-                                  RENDER_RATE_BUCKET_WIDTH);
+            clampToNearestBucket(info.renderRate ? *info.renderRate : info.refreshRate,
+                                 RENDER_RATE_BUCKET_WIDTH);
     const TimeStatsHelper::TimelineStatsKey timelineKey = {refreshRateBucket, renderRateBucket};
 
     if (!mTimeStats.stats.count(timelineKey)) {
@@ -1021,6 +1021,7 @@
 
 void TimeStats::clearAll() {
     std::lock_guard<std::mutex> lock(mMutex);
+    mTimeStats.stats.clear();
     clearGlobalLocked();
     clearLayersLocked();
 }
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 011ff70..965aac3 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -767,7 +767,8 @@
     EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
 }
 
-TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame) {
+// TODO (b/183181768): Fix & re-enable
+TEST_F(LayerCallbackTest, DISABLED_MultipleTransactions_SingleFrame) {
     sp<SurfaceControl> layer;
     ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
 
@@ -938,7 +939,8 @@
     EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected2, true));
 }
 
-TEST_F(LayerCallbackTest, DesiredPresentTime_OutOfOrder) {
+// TODO (b/183181768): Fix & re-enable
+TEST_F(LayerCallbackTest, DISABLED_DesiredPresentTime_OutOfOrder) {
     sp<SurfaceControl> layer;
     ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
 
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 7581cd3..c8eeac6 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -1336,7 +1336,8 @@
                                        Color::GREEN, true /* filtered */);
 }
 
-TEST_P(LayerRenderTypeTransactionTest, SetFenceBasic_BufferState) {
+// TODO (b/186543004): Fix & re-enable
+TEST_P(LayerRenderTypeTransactionTest, DISABLED_SetFenceBasic_BufferState) {
     sp<SurfaceControl> layer;
     Transaction transaction;
     ASSERT_NO_FATAL_FAILURE(
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 6ed6148..c6a4115 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -73,7 +73,7 @@
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
         maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
-        maxTokenRetentionTime = mTokenManager->kMaxRetentionTime;
+        maxTokens = mTokenManager->kMaxTokens;
     }
 
     // Each tracing session can be used for a single block of Start -> Stop.
@@ -111,9 +111,11 @@
         mFrameTimeline->setSfPresent(2500, presentFence1);
     }
 
-    void flushTokens(nsecs_t flushTime) {
-        std::lock_guard<std::mutex> lock(mTokenManager->mMutex);
-        mTokenManager->flushTokens(flushTime);
+    void flushTokens() {
+        for (size_t i = 0; i < maxTokens; i++) {
+            mTokenManager->generateTokenForPredictions({});
+        }
+        EXPECT_EQ(getPredictions().size(), maxTokens);
     }
 
     SurfaceFrame& getSurfaceFrame(size_t displayFrameIdx, size_t surfaceFrameIdx) {
@@ -132,7 +134,7 @@
                 a.presentTime == b.presentTime;
     }
 
-    const std::map<int64_t, TokenManagerPrediction>& getPredictions() const {
+    const std::map<int64_t, TimelineItem>& getPredictions() const {
         return mTokenManager->mPredictions;
     }
 
@@ -155,7 +157,7 @@
     TraceCookieCounter* mTraceCookieCounter;
     FenceToFenceTimeMap fenceFactory;
     uint32_t* maxDisplayFrames;
-    nsecs_t maxTokenRetentionTime;
+    size_t maxTokens;
     static constexpr pid_t kSurfaceFlingerPid = 666;
     static constexpr nsecs_t kPresentThreshold = std::chrono::nanoseconds(2ns).count();
     static constexpr nsecs_t kDeadlineThreshold = std::chrono::nanoseconds(2ns).count();
@@ -177,12 +179,11 @@
 TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
     int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
     EXPECT_EQ(getPredictions().size(), 1u);
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
     int64_t token2 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     std::optional<TimelineItem> predictions = mTokenManager->getPredictionsForToken(token1);
 
     // token1 should have expired
-    EXPECT_EQ(getPredictions().size(), 1u);
     EXPECT_EQ(predictions.has_value(), false);
 
     predictions = mTokenManager->getPredictionsForToken(token2);
@@ -212,7 +213,7 @@
 
 TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) {
     int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
                                                        sLayerIdOne, sLayerNameOne, sLayerNameOne,
@@ -707,7 +708,7 @@
                                                        sLayerNameOne, /*isBuffer*/ true);
     surfaceFrame1->setAcquireFenceTime(45);
     // Trigger a prediction expiry
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
     mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1065,7 +1066,7 @@
     tracingSession->StartBlocking();
     int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
     // Flush the token so that it would expire
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
@@ -1283,7 +1284,7 @@
             mTokenManager->generateTokenForPredictions({appStartTime, appEndTime, appPresentTime});
 
     // Flush the token so that it would expire
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0},
                                                        sPidOne, sUidOne, sLayerIdOne, sLayerNameOne,
@@ -1359,7 +1360,7 @@
             mTokenManager->generateTokenForPredictions({appStartTime, appEndTime, appPresentTime});
 
     // Flush the token so that it would expire
-    flushTokens(systemTime() + maxTokenRetentionTime);
+    flushTokens();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0},
                                                        sPidOne, sUidOne, sLayerIdOne, sLayerNameOne,
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 188ea75..ff53a7b 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -897,24 +897,24 @@
     EXPECT_THAT(result, HasSubstr("compositionStrategyChanges = 0"));
     EXPECT_THAT(result, HasSubstr("averageFrameDuration = 0.000 ms"));
     EXPECT_THAT(result, HasSubstr("averageRenderEngineTiming = 0.000 ms"));
-    std::string expectedResult = "totalTimelineFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "jankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "sfLongCpuJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "sfLongGpuJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "sfUnattributedJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "appUnattributedJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "sfSchedulingJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "sfPredictionErrorJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
-    expectedResult = "appBufferStuffingJankyFrames = " + std::to_string(0);
-    EXPECT_THAT(result, HasSubstr(expectedResult));
+    std::string expectedResult = "totalTimelineFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "jankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "sfLongCpuJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "sfLongGpuJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "sfUnattributedJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "appUnattributedJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "sfSchedulingJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "sfPredictionErrorJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
+    expectedResult = "appBufferStuffingJankyFrames = ";
+    EXPECT_THAT(result, Not(HasSubstr(expectedResult)));
 }
 
 TEST_F(TimeStatsTest, canDumpWithMaxLayers) {
@@ -1348,6 +1348,30 @@
     }
 }
 
+TEST_F(TimeStatsTest, refreshRateIsClampedToNearestBucket) {
+    // this stat is not in the proto so verify by checking the string dump
+    const auto verifyRefreshRateBucket = [&](Fps fps, int32_t bucket) {
+        EXPECT_TRUE(inputCommand(InputCommand::CLEAR, FMT_STRING).empty());
+        EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+        insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+        mTimeStats->incrementJankyFrames(
+                {fps, std::nullopt, UID_0, genLayerName(LAYER_ID_0), JankType::None, 0, 0, 0});
+        const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+        std::string expectedResult = "displayRefreshRate = " + std::to_string(bucket) + " fps";
+        EXPECT_THAT(result, HasSubstr(expectedResult)) << "failed for " << fps;
+    };
+
+    verifyRefreshRateBucket(Fps(91.f), 90);
+    verifyRefreshRateBucket(Fps(89.f), 90);
+
+    verifyRefreshRateBucket(Fps(61.f), 60);
+    verifyRefreshRateBucket(Fps(59.f), 60);
+
+    verifyRefreshRateBucket(Fps(31.f), 30);
+    verifyRefreshRateBucket(Fps(29.f), 30);
+}
+
 } // namespace
 } // namespace android
 
diff --git a/services/vr/hardware_composer/Android.bp b/services/vr/hardware_composer/Android.bp
index eb24a22..80e9a3c 100644
--- a/services/vr/hardware_composer/Android.bp
+++ b/services/vr/hardware_composer/Android.bp
@@ -106,42 +106,6 @@
     ],
 }
 
-cc_binary {
-    name: "vr_hwc",
-    enabled: false,
-    system_ext_specific: true,
-    vintf_fragments: ["manifest_vr_hwc.xml"],
-    srcs: [
-        "vr_hardware_composer_service.cpp",
-    ],
-    static_libs: [
-        "libvr_hwc-impl",
-        // NOTE: This needs to be included after the *-impl lib otherwise the
-        // symbols in the *-binder library get optimized out.
-        "libvr_hwc-binder",
-    ],
-    shared_libs: [
-        "android.frameworks.vr.composer@2.0",
-        "android.hardware.graphics.composer@2.3",
-        "libbase",
-        "libbinder",
-        "liblog",
-        "libhardware",
-        "libhidlbase",
-        "libui",
-        "libutils",
-        "libvr_hwc-hal",
-    ],
-    cflags: [
-        "-DLOG_TAG=\"vr_hwc\"",
-        "-Wall",
-        "-Werror",
-    ],
-    init_rc: [
-        "vr_hwc.rc",
-    ],
-}
-
 cc_test {
     name: "vr_hwc_test",
     gtest: true,
diff --git a/services/vr/hardware_composer/manifest_vr_hwc.xml b/services/vr/hardware_composer/manifest_vr_hwc.xml
deleted file mode 100644
index 1068cac..0000000
--- a/services/vr/hardware_composer/manifest_vr_hwc.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest version="1.0" type="framework">
-    <hal>
-      <name>android.hardware.graphics.composer</name>
-      <transport>hwbinder</transport>
-      <version>2.1</version>
-      <interface>
-          <name>IComposer</name>
-          <instance>vr</instance>
-      </interface>
-    </hal>
-</manifest>
diff --git a/services/vr/hardware_composer/vr_hardware_composer_service.cpp b/services/vr/hardware_composer/vr_hardware_composer_service.cpp
deleted file mode 100644
index 7701847..0000000
--- a/services/vr/hardware_composer/vr_hardware_composer_service.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <hwbinder/IPCThreadState.h>
-#include <impl/vr_hwc.h>
-#include <inttypes.h>
-
-#include "vr_composer.h"
-
-int main() {
-  android::ProcessState::self()->startThreadPool();
-
-  // Register the hwbinder HWC HAL service used by SurfaceFlinger while in VR
-  // mode.
-  android::sp<android::dvr::VrHwc> service = new android::dvr::VrHwc();
-
-  LOG_ALWAYS_FATAL_IF(!service.get(), "Failed to get service");
-  LOG_ALWAYS_FATAL_IF(service->isRemote(), "Service is remote");
-
-  const char instance[] = "vr";
-  LOG_ALWAYS_FATAL_IF(service->registerAsService(instance) != android::OK,
-                      "Failed to register service");
-
-  android::sp<android::dvr::VrComposer> composer =
-      new android::dvr::VrComposer(service.get());
-
-  android::sp<android::IServiceManager> sm(android::defaultServiceManager());
-
-  // Register the binder service used by VR Window Manager service to receive
-  // frame information from VR HWC HAL.
-  android::status_t status = sm->addService(
-      android::dvr::VrComposer::SERVICE_NAME(), composer.get(),
-      false /* allowIsolated */);
-  LOG_ALWAYS_FATAL_IF(status != android::OK,
-                      "VrDisplay service failed to start: %" PRId32, status);
-
-  android::hardware::ProcessState::self()->startThreadPool();
-  android::hardware::IPCThreadState::self()->joinThreadPool();
-
-  return 0;
-}
diff --git a/services/vr/hardware_composer/vr_hwc.rc b/services/vr/hardware_composer/vr_hwc.rc
deleted file mode 100644
index 645ab80..0000000
--- a/services/vr/hardware_composer/vr_hwc.rc
+++ /dev/null
@@ -1,6 +0,0 @@
-service vr_hwc /system/bin/vr_hwc
-  class hal animation
-  user system
-  group system graphics
-  onrestart restart surfaceflinger
-  writepid /dev/cpuset/system-background/tasks