Merge "Revert "EGL BlobCache: Support multifile cache and flexible size...""
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 6794fbf..43048db 100644
--- a/include/audiomanager/AudioManager.h
+++ b/include/audiomanager/AudioManager.h
@@ -40,9 +40,17 @@
     PLAYER_UPDATE_DEVICE_ID = 5,
     PLAYER_UPDATE_PORT_ID = 6,
     PLAYER_UPDATE_MUTED = 7,
+    PLAYER_UPDATE_FORMAT = 8,
 } player_state_t;
 
 static constexpr char
+    kExtraPlayerEventSpatializedKey[] = "android.media.extra.PLAYER_EVENT_SPATIALIZED";
+static constexpr char
+    kExtraPlayerEventSampleRateKey[] = "android.media.extra.PLAYER_EVENT_SAMPLE_RATE";
+static constexpr char
+    kExtraPlayerEventChannelMaskKey[] = "android.media.extra.PLAYER_EVENT_CHANNEL_MASK";
+
+static constexpr char
     kExtraPlayerEventMuteKey[] = "android.media.extra.PLAYER_EVENT_MUTE";
 enum {
     PLAYER_MUTE_MASTER = (1 << 0),
diff --git a/include/input/Input.h b/include/input/Input.h
index 015efdd..1a35196 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -203,6 +203,13 @@
  */
 vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy);
 
+/*
+ * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or
+ * pointing upwards in the negative Y direction, a positive angle points towards the right, and a
+ * negative angle points towards the left.
+ */
+float transformAngle(const ui::Transform& transform, float angleRadians);
+
 const char* inputEventTypeToString(int32_t type);
 
 std::string inputEventSourceToString(int32_t source);
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 28d1f16..d0de7b9 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -75,12 +75,48 @@
 AIBinder::AIBinder(const AIBinder_Class* clazz) : mClazz(clazz) {}
 AIBinder::~AIBinder() {}
 
-std::optional<bool> AIBinder::associateClassInternal(const AIBinder_Class* clazz,
-                                                     const String16& newDescriptor, bool set) {
+// b/175635923 libcxx causes "implicit-conversion" with a string with invalid char
+static std::string SanitizeString(const String16& str) {
+    std::string sanitized{String8(str)};
+    for (auto& c : sanitized) {
+        if (!isprint(c)) {
+            c = '?';
+        }
+    }
+    return sanitized;
+}
+
+bool AIBinder::associateClass(const AIBinder_Class* clazz) {
+    if (clazz == nullptr) return false;
+
+    // If mClazz is non-null, this must have been called and cached
+    // already. So, we can safely call this first. Due to the implementation
+    // of getInterfaceDescriptor (at time of writing), two simultaneous calls
+    // may lead to extra binder transactions, but this is expected to be
+    // exceedingly rare. Once we have a binder, when we get it again later,
+    // we won't make another binder transaction here.
+    const String16& descriptor = getBinder()->getInterfaceDescriptor();
+    const String16& newDescriptor = clazz->getInterfaceDescriptor();
+
     std::lock_guard<std::mutex> lock(mClazzMutex);
     if (mClazz == clazz) return true;
 
-    if (mClazz != nullptr) {
+    // If this is an ABpBinder, the first class object becomes the canonical one. The implication
+    // of this is that no API can require a proxy information to get information on how to behave.
+    // from the class itself - which should only store the interface descriptor. The functionality
+    // should be implemented by adding AIBinder_* APIs to set values on binders themselves, by
+    // setting things on AIBinder_Class which get transferred along with the binder, so that they
+    // can be read along with the BpBinder, or by modifying APIs directly (e.g. an option in
+    // onTransact).
+    //
+    // While this check is required to support linkernamespaces, one downside of it is that
+    // you may parcel code to communicate between things in the same process. However, comms
+    // between linkernamespaces like this already happen for cross-language calls like Java<->C++
+    // or Rust<->Java, and there are good stability guarantees here. This interacts with
+    // binder Stability checks exactly like any other in-process call. The stability is known
+    // to the IBinder object, so that it doesn't matter if a class object comes from
+    // a different stability level.
+    if (mClazz != nullptr && !asABpBinder()) {
         const String16& currentDescriptor = mClazz->getInterfaceDescriptor();
         if (newDescriptor == currentDescriptor) {
             LOG(ERROR) << __func__ << ": Class descriptors '" << currentDescriptor
@@ -97,37 +133,10 @@
         return false;
     }
 
-    if (set) {
-        // if this is a local object, it's not one known to libbinder_ndk
-        mClazz = clazz;
-        return true;
-    }
-
-    return {};
-}
-
-// b/175635923 libcxx causes "implicit-conversion" with a string with invalid char
-static std::string SanitizeString(const String16& str) {
-    std::string sanitized{String8(str)};
-    for (auto& c : sanitized) {
-        if (!isprint(c)) {
-            c = '?';
-        }
-    }
-    return sanitized;
-}
-
-bool AIBinder::associateClass(const AIBinder_Class* clazz) {
-    if (clazz == nullptr) return false;
-
-    const String16& newDescriptor = clazz->getInterfaceDescriptor();
-
-    auto result = associateClassInternal(clazz, newDescriptor, false);
-    if (result.has_value()) return *result;
-
-    CHECK(asABpBinder() != nullptr);  // ABBinder always has a descriptor
-
-    const String16& descriptor = getBinder()->getInterfaceDescriptor();
+    // This will always be an O(n) comparison, but it's expected to be extremely rare.
+    // since it's an error condition. Do the comparison after we take the lock and
+    // check the pointer equality fast path. By always taking the lock, it's also
+    // more flake-proof. However, the check is not dependent on the lock.
     if (descriptor != newDescriptor) {
         if (getBinder()->isBinderAlive()) {
             LOG(ERROR) << __func__ << ": Expecting binder to have class '" << newDescriptor
@@ -141,7 +150,14 @@
         return false;
     }
 
-    return associateClassInternal(clazz, newDescriptor, true).value();
+    // A local binder being set for the first time OR
+    // ignoring a proxy binder which is set multiple time, by considering the first
+    // associated class as the canonical one.
+    if (mClazz == nullptr) {
+        mClazz = clazz;
+    }
+
+    return true;
 }
 
 ABBinder::ABBinder(const AIBinder_Class* clazz, void* userData)
@@ -325,6 +341,10 @@
     return lhs->binder < rhs->binder;
 }
 
+// WARNING: When multiple classes exist with the same interface descriptor in different
+// linkernamespaces, the first one to be associated with mClazz becomes the canonical one
+// and the only requirement on this is that the interface descriptors match. If this
+// is an ABpBinder, no other state can be referenced from mClazz.
 AIBinder_Class::AIBinder_Class(const char* interfaceDescriptor, AIBinder_Class_onCreate onCreate,
                                AIBinder_Class_onDestroy onDestroy,
                                AIBinder_Class_onTransact onTransact)
@@ -632,6 +652,10 @@
     (*in)->get()->markForBinder(binder->getBinder());
 
     status_t status = android::OK;
+
+    // note - this is the only read of a value in clazz, and it comes with a warning
+    // on the API itself. Do not copy this design. Instead, attach data in a new
+    // version of the prepareTransaction function.
     if (clazz->writeHeader) {
         status = (*in)->get()->writeInterfaceToken(clazz->getInterfaceDescriptor());
     }
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index d7098e8..67bb092 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -53,12 +53,14 @@
     }
 
    private:
-    std::optional<bool> associateClassInternal(const AIBinder_Class* clazz,
-                                               const ::android::String16& newDescriptor, bool set);
-
     // AIBinder instance is instance of this class for a local object. In order to transact on a
     // remote object, this also must be set for simplicity (although right now, only the
     // interfaceDescriptor from it is used).
+    //
+    // WARNING: When multiple classes exist with the same interface descriptor in different
+    // linkernamespaces, the first one to be associated with mClazz becomes the canonical one
+    // and the only requirement on this is that the interface descriptors match. If this
+    // is an ABpBinder, no other state can be referenced from mClazz.
     const AIBinder_Class* mClazz;
     std::mutex mClazzMutex;
 };
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 1af2119..05677a8 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -229,6 +229,11 @@
  *
  * Available since API level 33.
  *
+ * WARNING: this API interacts badly with linkernamespaces. For correct behavior, you must
+ * use it on all instances of a class in the same process which share the same interface
+ * descriptor. In general, it is recommended you do not use this API, because it is disabling
+ * type safety.
+ *
  * \param clazz class to disable interface header on.
  */
 void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) __INTRODUCED_IN(33);
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 2afe5d2..76acff5 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -72,6 +72,11 @@
 AIBinder_Class* IFoo::kClass = AIBinder_Class_define(kIFooDescriptor, IFoo_Class_onCreate,
                                                      IFoo_Class_onDestroy, IFoo_Class_onTransact);
 
+// Defines the same class. Ordinarly, you would never want to do this, but it's done here
+// to simulate what would happen when multiple linker namespaces interact.
+AIBinder_Class* IFoo::kClassDupe = AIBinder_Class_define(
+        kIFooDescriptor, IFoo_Class_onCreate, IFoo_Class_onDestroy, IFoo_Class_onTransact);
+
 class BpFoo : public IFoo {
    public:
     explicit BpFoo(AIBinder* binder) : mBinder(binder) {}
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 7408d0c..0a562f0 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -30,8 +30,13 @@
     static const char* kIFooDescriptor;
 
     static AIBinder_Class* kClass;
+    static AIBinder_Class* kClassDupe;
 
     // binder representing this interface with one reference count
+    // NOTE - this will create a new binder if it already exists. If you use
+    // getService for instance, you must pull outBinder. Don't use this without
+    // verifying isRemote or pointer equality. This is not a very good testing API - don't
+    // copy it - consider the AIDL-generated APIs instead.
     AIBinder* getBinder();
 
     // Takes ownership of IFoo
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 9d5ef68..5b2532a 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -55,6 +55,18 @@
 constexpr unsigned int kShutdownWaitTime = 10;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
 
+class MyTestFoo : public IFoo {
+    binder_status_t doubleNumber(int32_t in, int32_t* out) override {
+        *out = 2 * in;
+        LOG(INFO) << "doubleNumber (" << in << ") => " << *out;
+        return STATUS_OK;
+    }
+    binder_status_t die() override {
+        ADD_FAILURE() << "die called on local instance";
+        return STATUS_OK;
+    }
+};
+
 class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
     ndk::ScopedAStatus repeatInt(int32_t in, int32_t* out) {
         *out = in;
@@ -296,11 +308,10 @@
 }
 
 TEST(NdkBinder, UnimplementedDump) {
-    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName);
+    ndk::SpAIBinder binder;
+    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
     ASSERT_NE(foo, nullptr);
-    AIBinder* binder = foo->getBinder();
-    EXPECT_EQ(OK, AIBinder_dump(binder, STDOUT_FILENO, nullptr, 0));
-    AIBinder_decStrong(binder);
+    EXPECT_EQ(OK, AIBinder_dump(binder.get(), STDOUT_FILENO, nullptr, 0));
 }
 
 TEST(NdkBinder, UnimplementedShell) {
@@ -324,6 +335,24 @@
     EXPECT_EQ(2, out);
 }
 
+TEST(NdkBinder, ReassociateBpBinderWithSameDescriptor) {
+    ndk::SpAIBinder binder;
+    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
+
+    EXPECT_TRUE(AIBinder_isRemote(binder.get()));
+
+    EXPECT_TRUE(AIBinder_associateClass(binder.get(), IFoo::kClassDupe));
+}
+
+TEST(NdkBinder, CantHaveTwoLocalBinderClassesWithSameDescriptor) {
+    sp<IFoo> foo = sp<MyTestFoo>::make();
+    ndk::SpAIBinder binder(foo->getBinder());
+
+    EXPECT_FALSE(AIBinder_isRemote(binder.get()));
+
+    EXPECT_FALSE(AIBinder_associateClass(binder.get(), IFoo::kClassDupe));
+}
+
 TEST(NdkBinder, GetTestServiceStressTest) {
     // libbinder has some complicated logic to make sure only one instance of
     // ABpBinder is associated with each binder.
@@ -545,18 +574,6 @@
     AIBinder_decStrong(binder);
 }
 
-class MyTestFoo : public IFoo {
-    binder_status_t doubleNumber(int32_t in, int32_t* out) override {
-        *out = 2 * in;
-        LOG(INFO) << "doubleNumber (" << in << ") => " << *out;
-        return STATUS_OK;
-    }
-    binder_status_t die() override {
-        ADD_FAILURE() << "die called on local instance";
-        return STATUS_OK;
-    }
-};
-
 TEST(NdkBinder, SetInheritRt) {
     // functional test in binderLibTest
     sp<IFoo> foo = sp<MyTestFoo>::make();
@@ -597,7 +614,8 @@
     sp<IFoo> foo = new MyTestFoo;
     EXPECT_EQ(EX_NONE, foo->addService(kInstanceName));
 
-    sp<IFoo> getFoo = IFoo::getService(kInstanceName);
+    ndk::SpAIBinder binder;
+    sp<IFoo> getFoo = IFoo::getService(kInstanceName, binder.getR());
     EXPECT_EQ(foo.get(), getFoo.get());
 
     int32_t out;
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
new file mode 100644
index 0000000..ab7a50d
--- /dev/null
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -0,0 +1,83 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+LIBBINDER_DIR := frameworks/native/libs/binder
+LIBBASE_DIR := system/libbase
+LIBCUTILS_DIR := system/core/libcutils
+LIBUTILS_DIR := system/core/libutils
+FMTLIB_DIR := external/fmtlib
+
+MODULE_SRCS := \
+	$(LOCAL_DIR)/../logging.cpp \
+	$(LOCAL_DIR)/../TrustyStatus.cpp \
+	$(LIBBINDER_DIR)/Binder.cpp \
+	$(LIBBINDER_DIR)/BpBinder.cpp \
+	$(LIBBINDER_DIR)/FdTrigger.cpp \
+	$(LIBBINDER_DIR)/IInterface.cpp \
+	$(LIBBINDER_DIR)/IResultReceiver.cpp \
+	$(LIBBINDER_DIR)/Parcel.cpp \
+	$(LIBBINDER_DIR)/Stability.cpp \
+	$(LIBBINDER_DIR)/Status.cpp \
+	$(LIBBINDER_DIR)/Utils.cpp \
+	$(LIBBASE_DIR)/hex.cpp \
+	$(LIBBASE_DIR)/stringprintf.cpp \
+	$(LIBUTILS_DIR)/Errors.cpp \
+	$(LIBUTILS_DIR)/misc.cpp \
+	$(LIBUTILS_DIR)/RefBase.cpp \
+	$(LIBUTILS_DIR)/StrongPointer.cpp \
+	$(LIBUTILS_DIR)/Unicode.cpp \
+
+# TODO: remove the following when libbinder supports std::string
+# instead of String16 and String8 for Status and descriptors
+MODULE_SRCS += \
+	$(LIBUTILS_DIR)/SharedBuffer.cpp \
+	$(LIBUTILS_DIR)/String16.cpp \
+	$(LIBUTILS_DIR)/String8.cpp \
+
+# TODO: disable dump() transactions to get rid of Vector
+MODULE_SRCS += \
+	$(LIBUTILS_DIR)/VectorImpl.cpp \
+
+MODULE_DEFINES += \
+	LK_DEBUGLEVEL_NO_ALIASES=1 \
+
+MODULE_INCLUDES += \
+	$(LOCAL_DIR)/.. \
+
+GLOBAL_INCLUDES += \
+	$(LOCAL_DIR)/include \
+	$(LOCAL_DIR)/../include \
+	$(LIBBINDER_DIR)/include \
+	$(LIBBINDER_DIR)/ndk/include_cpp \
+	$(LIBBASE_DIR)/include \
+	$(LIBCUTILS_DIR)/include \
+	$(LIBUTILS_DIR)/include \
+	$(FMTLIB_DIR)/include \
+
+GLOBAL_COMPILEFLAGS += \
+	-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION \
+	-DBINDER_NO_KERNEL_IPC \
+	-DBINDER_RPC_SINGLE_THREADED \
+	-D__ANDROID_VNDK__ \
+
+MODULE_DEPS += \
+	trusty/kernel/lib/libcxx-trusty \
+	trusty/kernel/lib/libcxxabi-trusty \
+
+include make/module.mk
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 9e8ebf3..d893cb9 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -46,25 +46,6 @@
 
 namespace {
 
-float transformAngle(const ui::Transform& transform, float angleRadians) {
-    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
-    // Coordinate system: down is increasing Y, right is increasing X.
-    float x = sinf(angleRadians);
-    float y = -cosf(angleRadians);
-    vec2 transformedPoint = transform.transform(x, y);
-
-    // Determine how the origin is transformed by the matrix so that we
-    // can transform orientation vectors.
-    const vec2 origin = transform.transform(0, 0);
-
-    transformedPoint.x -= origin.x;
-    transformedPoint.y -= origin.y;
-
-    // Derive the transformed vector's clockwise angle from vertical.
-    // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
-    return atan2f(transformedPoint.x, -transformedPoint.y);
-}
-
 bool shouldDisregardTransformation(uint32_t source) {
     // Do not apply any transformations to axes from joysticks, touchpads, or relative mice.
     return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) ||
@@ -172,6 +153,25 @@
     return transformedXy - transformedOrigin;
 }
 
+float transformAngle(const ui::Transform& transform, float angleRadians) {
+    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+    // Coordinate system: down is increasing Y, right is increasing X.
+    float x = sinf(angleRadians);
+    float y = -cosf(angleRadians);
+    vec2 transformedPoint = transform.transform(x, y);
+
+    // Determine how the origin is transformed by the matrix so that we
+    // can transform orientation vectors.
+    const vec2 origin = transform.transform(0, 0);
+
+    transformedPoint.x -= origin.x;
+    transformedPoint.y -= origin.y;
+
+    // Derive the transformed vector's clockwise angle from vertical.
+    // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
+    return atan2f(transformedPoint.x, -transformedPoint.y);
+}
+
 const char* inputEventTypeToString(int32_t type) {
     switch (type) {
         case AINPUT_EVENT_TYPE_KEY: {
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 8d19c45..b8fd1b2 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -133,6 +133,8 @@
         "-fvisibility=hidden",
         "-Werror=format",
         "-Wno-unused-parameter",
+        // TODO: Investigate reducing pinned-memory usage (b/263377839)
+        "-DRE_SKIAVK",
     ],
     srcs: [
         ":librenderengine_sources",
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 160f9eb..9a7af40 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -51,9 +51,8 @@
     return base::StringPrintf("%dx%d", size.width, size.height);
 }
 
-static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) {
-    // Consider all four sides as "inclusive".
-    return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
+static bool isPointInRect(const Rect& rect, vec2 p) {
+    return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
 }
 
 template <typename T>
@@ -81,29 +80,12 @@
     return value >= 8 ? value - 16 : value;
 }
 
-static std::tuple<ui::Size /*displayBounds*/, Rect /*physicalFrame*/> getNaturalDisplayInfo(
-        const DisplayViewport& viewport, ui::Rotation naturalOrientation) {
+static ui::Size getNaturalDisplaySize(const DisplayViewport& viewport) {
     ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight};
-    if (naturalOrientation == ui::ROTATION_90 || naturalOrientation == ui::ROTATION_270) {
+    if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
         std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height);
     }
-
-    ui::Transform rotate(ui::Transform::toRotationFlags(naturalOrientation),
-                         rotatedDisplaySize.width, rotatedDisplaySize.height);
-
-    Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight,
-                       viewport.physicalBottom};
-    physicalFrame = rotate.transform(physicalFrame);
-
-    LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid());
-    if (physicalFrame.isEmpty()) {
-        ALOGE("Viewport is not set properly: %s", viewport.toString().c_str());
-        physicalFrame.right =
-                physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width());
-        physicalFrame.bottom =
-                physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height());
-    }
-    return {rotatedDisplaySize, physicalFrame};
+    return rotatedDisplaySize;
 }
 
 // --- RawPointerData ---
@@ -197,18 +179,6 @@
     if (mCursorScrollAccumulator.haveRelativeHWheel()) {
         info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
-    if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
-        const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
-        const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, x.fuzz,
-                             x.resolution);
-        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, y.fuzz,
-                             y.resolution);
-        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, x.fuzz,
-                             x.resolution);
-        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, y.fuzz,
-                             y.resolution);
-    }
     info->setButtonUnderPad(mParameters.hasButtonUnderPad);
     info->setSupportsUsi(mParameters.supportsUsi);
 }
@@ -224,10 +194,10 @@
     dumpDisplay(dump);
 
     dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n");
-    dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale);
-    dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale);
-    dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
-    dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+    mRawToDisplay.dump(dump, "RawToDisplay Transform:", INDENT4);
+    mRawRotation.dump(dump, "RawRotation Transform:", INDENT4);
+    dump += StringPrintf(INDENT4 "OrientedXPrecision: %0.3f\n", mOrientedXPrecision);
+    dump += StringPrintf(INDENT4 "OrientedYPrecision: %0.3f\n", mOrientedYPrecision);
     dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
     dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
     dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
@@ -687,10 +657,10 @@
 
 void TouchInputMapper::initializeOrientedRanges() {
     // Configure X and Y factors.
-    mXScale = float(mDisplayBounds.width) / mRawPointerAxes.getRawWidth();
-    mYScale = float(mDisplayBounds.height) / mRawPointerAxes.getRawHeight();
-    mXPrecision = 1.0f / mXScale;
-    mYPrecision = 1.0f / mYScale;
+    const float orientedScaleX = mRawToDisplay.getScaleX();
+    const float orientedScaleY = mRawToDisplay.getScaleY();
+    mOrientedXPrecision = 1.0f / orientedScaleX;
+    mOrientedYPrecision = 1.0f / orientedScaleY;
 
     mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
     mOrientedRanges.x.source = mSource;
@@ -700,7 +670,7 @@
     // Scale factor for terms that are not oriented in a particular axis.
     // If the pixels are square then xScale == yScale otherwise we fake it
     // by choosing an average.
-    mGeometricScale = avg(mXScale, mYScale);
+    mGeometricScale = avg(orientedScaleX, orientedScaleY);
 
     initializeSizeRanges();
 
@@ -817,44 +787,74 @@
     // Compute oriented precision, scales and ranges.
     // Note that the maximum value reported is an inclusive maximum value so it is one
     // unit less than the total width or height of the display.
+    // TODO(b/20508709): Calculate the oriented ranges using the input device's raw frame.
     switch (mInputDeviceOrientation) {
         case ui::ROTATION_90:
         case ui::ROTATION_270:
-            mOrientedXPrecision = mYPrecision;
-            mOrientedYPrecision = mXPrecision;
-
             mOrientedRanges.x.min = 0;
             mOrientedRanges.x.max = mDisplayBounds.height - 1;
             mOrientedRanges.x.flat = 0;
             mOrientedRanges.x.fuzz = 0;
-            mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+            mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
 
             mOrientedRanges.y.min = 0;
             mOrientedRanges.y.max = mDisplayBounds.width - 1;
             mOrientedRanges.y.flat = 0;
             mOrientedRanges.y.fuzz = 0;
-            mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+            mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
             break;
 
         default:
-            mOrientedXPrecision = mXPrecision;
-            mOrientedYPrecision = mYPrecision;
-
             mOrientedRanges.x.min = 0;
             mOrientedRanges.x.max = mDisplayBounds.width - 1;
             mOrientedRanges.x.flat = 0;
             mOrientedRanges.x.fuzz = 0;
-            mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+            mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
 
             mOrientedRanges.y.min = 0;
             mOrientedRanges.y.max = mDisplayBounds.height - 1;
             mOrientedRanges.y.flat = 0;
             mOrientedRanges.y.fuzz = 0;
-            mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+            mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
             break;
     }
 }
 
+void TouchInputMapper::computeInputTransforms() {
+    const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
+
+    ui::Size rotatedRawSize = rawSize;
+    if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) {
+        std::swap(rotatedRawSize.width, rotatedRawSize.height);
+    }
+    const auto rotationFlags = ui::Transform::toRotationFlags(-mInputDeviceOrientation);
+    mRawRotation = ui::Transform{rotationFlags};
+
+    // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0).
+    ui::Transform undoRawOffset;
+    undoRawOffset.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+
+    // Step 2: Rotate the raw coordinates to the expected orientation.
+    ui::Transform rotate;
+    // When rotating raw coordinates, the raw size will be used as an offset.
+    // Account for the extra unit added to the raw range when the raw size was calculated.
+    rotate.set(rotationFlags, rotatedRawSize.width - 1, rotatedRawSize.height - 1);
+
+    // Step 3: Scale the raw coordinates to the display space.
+    ui::Transform scaleToDisplay;
+    const float xScale = static_cast<float>(mDisplayBounds.width) / rotatedRawSize.width;
+    const float yScale = static_cast<float>(mDisplayBounds.height) / rotatedRawSize.height;
+    scaleToDisplay.set(xScale, 0, 0, yScale);
+
+    mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset));
+
+    // Calculate the transform that takes raw coordinates to the rotated display space.
+    ui::Transform displayToRotatedDisplay;
+    displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation),
+                                mViewport.deviceWidth, mViewport.deviceHeight);
+    mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay;
+}
+
 void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
     const DeviceMode oldDeviceMode = mDeviceMode;
 
@@ -926,14 +926,9 @@
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
             const auto oldDisplayBounds = mDisplayBounds;
 
-            // Apply the inverse of the input device orientation so that the input device is
-            // configured in the same orientation as the viewport. The input device orientation will
-            // be re-applied by mInputDeviceOrientation.
-            const ui::Rotation naturalDeviceOrientation =
-                    mViewport.orientation - mParameters.orientation;
-
-            std::tie(mDisplayBounds, mPhysicalFrameInDisplay) =
-                    getNaturalDisplayInfo(mViewport, naturalDeviceOrientation);
+            mDisplayBounds = getNaturalDisplaySize(mViewport);
+            mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop,
+                                              mViewport.physicalRight, mViewport.physicalBottom};
 
             // InputReader works in the un-rotated display coordinate space, so we don't need to do
             // anything if the device is already orientation-aware. If the device is not
@@ -950,10 +945,14 @@
 
             // Apply the input device orientation for the device.
             mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation;
+            computeInputTransforms();
         } else {
             mDisplayBounds = rawSize;
-            mPhysicalFrameInDisplay = Rect{mDisplayBounds};
+            mPhysicalFrameInRotatedDisplay = Rect{mDisplayBounds};
             mInputDeviceOrientation = ui::ROTATION_0;
+            mRawToDisplay.reset();
+            mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+            mRawToRotatedDisplay = mRawToDisplay;
         }
     }
 
@@ -1036,7 +1035,8 @@
 void TouchInputMapper::dumpDisplay(std::string& dump) {
     dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
     dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
-    dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str());
+    dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
+                         toString(mPhysicalFrameInRotatedDisplay).c_str());
     dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
 }
 
@@ -1197,19 +1197,6 @@
     if (in.tryGetProperty("touch.distance.scale", distanceScale)) {
         out.distanceScale = distanceScale;
     }
-
-    out.coverageCalibration = Calibration::CoverageCalibration::DEFAULT;
-    std::string coverageCalibrationString;
-    if (in.tryGetProperty("touch.coverage.calibration", coverageCalibrationString)) {
-        if (coverageCalibrationString == "none") {
-            out.coverageCalibration = Calibration::CoverageCalibration::NONE;
-        } else if (coverageCalibrationString == "box") {
-            out.coverageCalibration = Calibration::CoverageCalibration::BOX;
-        } else if (coverageCalibrationString != "default") {
-            ALOGW("Invalid value for touch.coverage.calibration: '%s'",
-                  coverageCalibrationString.c_str());
-        }
-    }
 }
 
 void TouchInputMapper::resolveCalibration() {
@@ -1248,11 +1235,6 @@
     } else {
         mCalibration.distanceCalibration = Calibration::DistanceCalibration::NONE;
     }
-
-    // Coverage
-    if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::DEFAULT) {
-        mCalibration.coverageCalibration = Calibration::CoverageCalibration::NONE;
-    }
 }
 
 void TouchInputMapper::dumpCalibration(std::string& dump) {
@@ -1323,17 +1305,6 @@
     if (mCalibration.distanceScale) {
         dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", *mCalibration.distanceScale);
     }
-
-    switch (mCalibration.coverageCalibration) {
-        case Calibration::CoverageCalibration::NONE:
-            dump += INDENT4 "touch.coverage.calibration: none\n";
-            break;
-        case Calibration::CoverageCalibration::BOX:
-            dump += INDENT4 "touch.coverage.calibration: box\n";
-            break;
-        default:
-            ALOG_ASSERT(false);
-    }
 }
 
 void TouchInputMapper::dumpAffineTransformation(std::string& dump) {
@@ -2290,20 +2261,20 @@
         if (mHaveTilt) {
             float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
             float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
-            orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+            orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)));
             tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
         } else {
             tilt = 0;
 
             switch (mCalibration.orientationCalibration) {
                 case Calibration::OrientationCalibration::INTERPOLATED:
-                    orientation = in.orientation * mOrientationScale;
+                    orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale);
                     break;
                 case Calibration::OrientationCalibration::VECTOR: {
                     int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
                     int32_t c2 = signExtendNybble(in.orientation & 0x0f);
                     if (c1 != 0 || c2 != 0) {
-                        orientation = atan2f(c1, c2) * 0.5f;
+                        orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f);
                         float confidence = hypotf(c1, c2);
                         float scale = 1.0f + confidence / 16.0f;
                         touchMajor *= scale;
@@ -2330,76 +2301,16 @@
                 distance = 0;
         }
 
-        // Coverage
-        int32_t rawLeft, rawTop, rawRight, rawBottom;
-        switch (mCalibration.coverageCalibration) {
-            case Calibration::CoverageCalibration::BOX:
-                rawLeft = (in.toolMinor & 0xffff0000) >> 16;
-                rawRight = in.toolMinor & 0x0000ffff;
-                rawBottom = in.toolMajor & 0x0000ffff;
-                rawTop = (in.toolMajor & 0xffff0000) >> 16;
-                break;
-            default:
-                rawLeft = rawTop = rawRight = rawBottom = 0;
-                break;
-        }
-
-        // Adjust X,Y coords for device calibration
-        // TODO: Adjust coverage coords?
-        float xTransformed = in.x, yTransformed = in.y;
-        mAffineTransform.applyTo(xTransformed, yTransformed);
-        rotateAndScale(xTransformed, yTransformed);
-
-        // Adjust X, Y, and coverage coords for input device orientation.
-        float left, top, right, bottom;
-
-        switch (mInputDeviceOrientation) {
-            case ui::ROTATION_90:
-                left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
-                right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
-                bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
-                top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
-                orientation -= M_PI_2;
-                if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) {
-                    orientation +=
-                            (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
-                }
-                break;
-            case ui::ROTATION_180:
-                left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
-                right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
-                bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
-                top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
-                orientation -= M_PI;
-                if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) {
-                    orientation +=
-                            (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
-                }
-                break;
-            case ui::ROTATION_270:
-                left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
-                right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
-                bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
-                top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
-                orientation += M_PI_2;
-                if (mOrientedRanges.orientation && orientation > mOrientedRanges.orientation->max) {
-                    orientation -=
-                            (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
-                }
-                break;
-            default:
-                left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
-                right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
-                bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
-                top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
-                break;
-        }
+        // Adjust X,Y coords for device calibration and convert to the natural display coordinates.
+        vec2 transformed = {in.x, in.y};
+        mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/);
+        transformed = mRawToDisplay.transform(transformed);
 
         // Write output coords.
         PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
         out.clear();
-        out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed);
-        out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed);
+        out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x);
+        out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y);
         out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
         out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
         out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
@@ -2407,23 +2318,16 @@
         out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
         out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
         out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
-        if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
-            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
-            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
-            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
-            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
-        } else {
-            out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
-            out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
-        }
+        out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+        out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
 
         // Write output relative fields if applicable.
         uint32_t id = in.id;
         if (mSource == AINPUT_SOURCE_TOUCHPAD &&
             mLastCookedState.cookedPointerData.hasPointerCoordsForId(id)) {
             const PointerCoords& p = mLastCookedState.cookedPointerData.pointerCoordsForId(id);
-            float dx = xTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_X);
-            float dy = yTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_Y);
+            float dx = transformed.x - p.getAxisValue(AMOTION_EVENT_AXIS_X);
+            float dy = transformed.y - p.getAxisValue(AMOTION_EVENT_AXIS_Y);
             out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, dx);
             out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
         }
@@ -3796,48 +3700,10 @@
     return out;
 }
 
-// Transform input device coordinates to display panel coordinates.
-void TouchInputMapper::rotateAndScale(float& x, float& y) const {
-    const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale;
-    const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale;
-
-    const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale;
-    const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale;
-
-    // Rotate to display coordinate.
-    // 0 - no swap and reverse.
-    // 90 - swap x/y and reverse y.
-    // 180 - reverse x, y.
-    // 270 - swap x/y and reverse x.
-    switch (mInputDeviceOrientation) {
-        case ui::ROTATION_0:
-            x = xScaled;
-            y = yScaled;
-            break;
-        case ui::ROTATION_90:
-            y = xScaledMax;
-            x = yScaled;
-            break;
-        case ui::ROTATION_180:
-            x = xScaledMax;
-            y = yScaledMax;
-            break;
-        case ui::ROTATION_270:
-            y = xScaled;
-            x = yScaledMax;
-            break;
-        default:
-            assert(false);
-    }
-}
-
 bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const {
-    const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale;
-    const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
-
     return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
             y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
-            isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled);
+            isPointInRect(mPhysicalFrameInRotatedDisplay, mRawToRotatedDisplay.transform(x, y));
 }
 
 const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 50a7ea3..6e35b46 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -291,14 +291,6 @@
         DistanceCalibration distanceCalibration;
         std::optional<float> distanceScale;
 
-        enum class CoverageCalibration {
-            DEFAULT,
-            NONE,
-            BOX,
-        };
-
-        CoverageCalibration coverageCalibration;
-
         inline void applySizeScaleAndBias(float& outSize) const {
             if (sizeScale) {
                 outSize *= *sizeScale;
@@ -410,21 +402,26 @@
     // Always starts at (0, 0).
     ui::Size mDisplayBounds{ui::kInvalidSize};
 
-    // The physical frame is the rectangle in the natural display's coordinate space that maps to
+    // The physical frame is the rectangle in the rotated display's coordinate space that maps to
     // the logical display frame.
-    Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT};
+    Rect mPhysicalFrameInRotatedDisplay{Rect::INVALID_RECT};
 
     // The orientation of the input device relative to that of the display panel. It specifies
     // the rotation of the input device coordinates required to produce the display panel
     // orientation, so it will depend on whether the device is orientation aware.
     ui::Rotation mInputDeviceOrientation;
 
-    // Translation and scaling factors, orientation-independent.
-    float mXScale;
-    float mXPrecision;
+    // The transform that maps the input device's raw coordinate space to the un-rotated display's
+    // coordinate space. InputReader generates events in the un-rotated display's coordinate space.
+    ui::Transform mRawToDisplay;
 
-    float mYScale;
-    float mYPrecision;
+    // The transform that maps the input device's raw coordinate space to the rotated display's
+    // coordinate space. This used to perform hit-testing of raw events with the physical frame in
+    // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay.
+    ui::Transform mRawToRotatedDisplay;
+
+    // The transform used for non-planar raw axes, such as orientation and tilt.
+    ui::Transform mRawRotation;
 
     float mGeometricScale;
 
@@ -813,7 +810,7 @@
 
     static void assignPointerIds(const RawState& last, RawState& current);
 
-    void rotateAndScale(float& x, float& y) const;
+    void computeInputTransforms();
 
     void configureDeviceType();
 };
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index ef2080f..96d27b8 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -5553,7 +5553,7 @@
     // Rotation 90.
     clearViewports();
     prepareDisplay(ui::ROTATION_90);
-    processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN);
+    processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5581,7 +5581,7 @@
     // Rotation 270.
     clearViewports();
     prepareDisplay(ui::ROTATION_270);
-    processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50));
+    processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5718,7 +5718,7 @@
     // Orientation 90, Rotation 90.
     clearViewports();
     prepareDisplay(ui::ROTATION_90);
-    processDown(mapper, toRotatedRawX(50), toRotatedRawY(75));
+    processDown(mapper, toRawX(50), toRawY(75));
     processSync(mapper);
 
     EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5746,8 +5746,7 @@
     // Orientation 90, Rotation 270.
     clearViewports();
     prepareDisplay(ui::ROTATION_270);
-    processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN,
-                RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN);
+    processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
     processSync(mapper);
 
     EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5759,6 +5758,61 @@
     EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
 }
 
+TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "1");
+    prepareDisplay(ui::ROTATION_0);
+    auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // Set a physical frame in the display viewport.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->physicalLeft = 20;
+    viewport->physicalTop = 600;
+    viewport->physicalRight = 30;
+    viewport->physicalBottom = 610;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    // Start the touch.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+    processSync(mapper);
+
+    // Expect all input starting outside the physical frame to be ignored.
+    const std::array<Point, 6> outsidePoints = {
+            {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}};
+    for (const auto& p : outsidePoints) {
+        processMove(mapper, toRawX(p.x), toRawY(p.y));
+        processSync(mapper);
+        EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+
+    // Move the touch into the physical frame.
+    processMove(mapper, toRawX(25), toRawY(605));
+    processSync(mapper);
+    NotifyMotionArgs args;
+    EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    EXPECT_NEAR(25, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+    EXPECT_NEAR(605, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+    // Once the touch down is reported, continue reporting input, even if it is outside the frame.
+    for (const auto& p : outsidePoints) {
+        processMove(mapper, toRawX(p.x), toRawY(p.y));
+        processSync(mapper);
+        EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+        EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+        EXPECT_NEAR(p.x, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+        EXPECT_NEAR(p.y, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+    }
+
+    processUp(mapper);
+    processSync(mapper);
+    EXPECT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+}
+
 TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 4f5842a..14d08f8 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -61,7 +61,7 @@
             std::chrono::nanoseconds(800us).count();
 
     // The lowest Render Frame Rate that will ever be selected
-    static constexpr Fps kMinSupportedFrameRate = 20_Hz;
+    static constexpr Fps kMinSupportedFrameRate = 1_Hz;
 
     class Policy {
         static constexpr int kAllowGroupSwitchingDefault = false;
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a3b3c4c..79d02dd 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -141,6 +141,12 @@
     RefreshRateSelectorTest();
     ~RefreshRateSelectorTest();
 
+    // Represents the number of refresh rates possible
+    // from 1_Hz to 90_hz, including fractional rates.
+    static constexpr size_t kTotalRefreshRates120 = 120;
+    // Represents the number of refresh rates possible
+    // from 1_Hz to 120_hz, including fractional rates.
+    static constexpr size_t kTotalRefreshRates216 = 216;
     static constexpr DisplayModeId kModeId60{0};
     static constexpr DisplayModeId kModeId90{1};
     static constexpr DisplayModeId kModeId72{2};
@@ -1129,7 +1135,12 @@
                 return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
         }
     }();
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1155,10 +1166,18 @@
             case Config::FrameRateOverride::AppOverride:
                 return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
             case Config::FrameRateOverride::Enabled:
-                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+                return {{1_Hz, kMode30},
+                        {1.011_Hz, kMode90},
+                        {1.016_Hz, kMode60},
+                        {1.022_Hz, kMode90}};
         }
     }();
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1250,7 +1269,12 @@
                         {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
         }
     }();
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1264,7 +1288,11 @@
             selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
 
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1284,7 +1312,11 @@
             selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
 
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1309,7 +1341,12 @@
                         {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
         }
     }();
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
+    } else {
+        ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+    }
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
@@ -1562,7 +1599,11 @@
     }();
 
     auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
-    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
+    } else {
+        ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    }
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
@@ -1604,7 +1645,11 @@
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
+    } else {
+        ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    }
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
@@ -1644,7 +1689,11 @@
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
+    } else {
+        ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    }
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
@@ -1687,7 +1736,11 @@
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
+    } else {
+        ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+    }
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
@@ -2317,7 +2370,8 @@
 }
 
 // b/190578904
-TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_Heuristic) {
     if (g_noSlowTests) {
         GTEST_SKIP();
     }
@@ -2346,8 +2400,101 @@
     for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
         const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::Heuristic);
+    }
+}
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitDefault) {
+    if (g_noSlowTests) {
+        GTEST_SKIP();
+    }
+
+    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
+    constexpr int kMaxRefreshRate = 240;
+
+    DisplayModes displayModes;
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const DisplayModeId modeId(fps);
+        displayModes.try_emplace(modeId,
+                                 createDisplayMode(modeId,
+                                                   Fps::fromValue(static_cast<float>(fps))));
+    }
+
+    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+        layers[0].desiredRefreshRate = fps;
+        layers[0].vote = vote;
+        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+                << "Failed for " << ftl::enum_string(vote);
+    };
+
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
+    }
+}
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExactOrMultiple) {
+    if (g_noSlowTests) {
+        GTEST_SKIP();
+    }
+
+    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
+    constexpr int kMaxRefreshRate = 240;
+
+    DisplayModes displayModes;
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const DisplayModeId modeId(fps);
+        displayModes.try_emplace(modeId,
+                                 createDisplayMode(modeId,
+                                                   Fps::fromValue(static_cast<float>(fps))));
+    }
+
+    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+        layers[0].desiredRefreshRate = fps;
+        layers[0].vote = vote;
+        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+                << "Failed for " << ftl::enum_string(vote);
+    };
+
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
+    }
+}
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExact) {
+    if (g_noSlowTests) {
+        GTEST_SKIP();
+    }
+
+    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
+    constexpr int kMaxRefreshRate = 240;
+
+    DisplayModes displayModes;
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const DisplayModeId modeId(fps);
+        displayModes.try_emplace(modeId,
+                                 createDisplayMode(modeId,
+                                                   Fps::fromValue(static_cast<float>(fps))));
+    }
+
+    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+        layers[0].desiredRefreshRate = fps;
+        layers[0].vote = vote;
+        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+                << "Failed for " << ftl::enum_string(vote);
+    };
+
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
     }
 }
@@ -2807,13 +2954,18 @@
                         {90_Hz, 90_Hz},
                         {120_Hz, 120_Hz}};
             case Config::FrameRateOverride::Enabled:
-                return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
-                        {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz},  {120_Hz, 120_Hz}};
+                return {{1_Hz, 30_Hz},       {1.008_Hz, 120_Hz}, {1.011_Hz, 90_Hz},
+                        {1.014_Hz, 72_Hz},   {1.016_Hz, 60_Hz},  {1.022_Hz, 90_Hz},
+                        {1.0256_Hz, 120_Hz}, {1.028_Hz, 72_Hz}};
         }
     }();
 
     const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
-    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        ASSERT_EQ(kTotalRefreshRates216, primaryRefreshRates.size());
+    } else {
+        ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    }
 
     for (size_t i = 0; i < expected.size(); i++) {
         const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 9ed992b..273cdd5 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -636,6 +636,7 @@
             case ProcHook::EXT_swapchain_colorspace:
             case ProcHook::KHR_get_surface_capabilities2:
             case ProcHook::GOOGLE_surfaceless_query:
+            case ProcHook::EXT_surface_maintenance1:
                 hook_extensions_.set(ext_bit);
                 // return now as these extensions do not require HAL support
                 return;
@@ -657,9 +658,11 @@
             case ProcHook::KHR_shared_presentable_image:
             case ProcHook::KHR_swapchain:
             case ProcHook::EXT_hdr_metadata:
+            case ProcHook::EXT_swapchain_maintenance1:
             case ProcHook::ANDROID_external_memory_android_hardware_buffer:
             case ProcHook::ANDROID_native_buffer:
             case ProcHook::GOOGLE_display_timing:
+            case ProcHook::KHR_external_fence_fd:
             case ProcHook::EXTENSION_CORE_1_0:
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
@@ -690,16 +693,22 @@
                 ext_bit = ProcHook::ANDROID_native_buffer;
                 break;
             case ProcHook::KHR_incremental_present:
-            case ProcHook::GOOGLE_display_timing:
             case ProcHook::KHR_shared_presentable_image:
+            case ProcHook::GOOGLE_display_timing:
                 hook_extensions_.set(ext_bit);
                 // return now as these extensions do not require HAL support
                 return;
+            case ProcHook::EXT_swapchain_maintenance1:
+                // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd
+                name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME;
+                ext_bit = ProcHook::KHR_external_fence_fd;
+                break;
             case ProcHook::EXT_hdr_metadata:
             case ProcHook::KHR_bind_memory2:
                 hook_extensions_.set(ext_bit);
                 break;
             case ProcHook::ANDROID_external_memory_android_hardware_buffer:
+            case ProcHook::KHR_external_fence_fd:
             case ProcHook::EXTENSION_UNKNOWN:
                 // Extensions we don't need to do anything about at this level
                 break;
@@ -715,6 +724,7 @@
             case ProcHook::KHR_surface_protected_capabilities:
             case ProcHook::EXT_debug_report:
             case ProcHook::EXT_swapchain_colorspace:
+            case ProcHook::EXT_surface_maintenance1:
             case ProcHook::GOOGLE_surfaceless_query:
             case ProcHook::ANDROID_native_buffer:
             case ProcHook::EXTENSION_CORE_1_0:
@@ -747,10 +757,18 @@
         if (strcmp(name, props.extensionName) != 0)
             continue;
 
+        if (ext_bit != ProcHook::EXTENSION_UNKNOWN &&
+                hal_extensions_.test(ext_bit)) {
+            ALOGI("CreateInfoWrapper::FilterExtension: already have '%s'.", name);
+            continue;
+        }
+
         filter.names[filter.name_count++] = name;
         if (ext_bit != ProcHook::EXTENSION_UNKNOWN) {
             if (ext_bit == ProcHook::ANDROID_native_buffer)
                 hook_extensions_.set(ProcHook::KHR_swapchain);
+            if (ext_bit == ProcHook::KHR_external_fence_fd)
+                hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1);
 
             hal_extensions_.set(ext_bit);
         }
@@ -940,6 +958,9 @@
          VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION});
     loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME,
                                  VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION});
+    loader_extensions.push_back({
+        VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME,
+        VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION});
 
     static const VkExtensionProperties loader_debug_report_extension = {
         VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION,
@@ -1072,6 +1093,33 @@
     return result;
 }
 
+bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) {
+    const auto& driver = GetData(physicalDevice).driver;
+    if (!driver.GetPhysicalDeviceExternalFenceProperties)
+        return false;
+
+    // Requires support for external fences imported from sync fds.
+    // This is _almost_ universal on Android, but may be missing on
+    // some extremely old drivers, or on strange implementations like
+    // cuttlefish.
+    VkPhysicalDeviceExternalFenceInfo fenceInfo = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO,
+        nullptr,
+        VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT
+    };
+    VkExternalFenceProperties fenceProperties = {
+        VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES,
+        nullptr,
+        0, 0, 0
+    };
+
+    GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties);
+    if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT)
+        return true;
+
+    return false;
+}
+
 VkResult EnumerateDeviceExtensionProperties(
     VkPhysicalDevice physicalDevice,
     const char* pLayerName,
@@ -1149,6 +1197,12 @@
              VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION});
     }
 
+    if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) {
+        loader_extensions.push_back({
+                VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME,
+                VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION});
+    }
+
     // enumerate our extensions first
     if (!pLayerName && pProperties) {
         uint32_t count = std::min(
@@ -1644,6 +1698,12 @@
                 imageCompressionControlSwapchainInChain = true;
             } break;
 
+            case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: {
+                auto smf = reinterpret_cast<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT *>(
+                        pFeats);
+                smf->swapchainMaintenance1 = true;
+            } break;
+
             default:
                 break;
         }
diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp
index de98aa7..798af5c 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -162,6 +162,15 @@
     }
 }
 
+VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+    if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) {
+        return ReleaseSwapchainImagesEXT(device, pReleaseInfo);
+    } else {
+        Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed.");
+        return VK_SUCCESS;
+    }
+}
+
 // clang-format on
 
 const ProcHook g_proc_hooks[] = {
@@ -545,6 +554,13 @@
         nullptr,
     },
     {
+        "vkReleaseSwapchainImagesEXT",
+        ProcHook::DEVICE,
+        ProcHook::EXT_swapchain_maintenance1,
+        reinterpret_cast<PFN_vkVoidFunction>(ReleaseSwapchainImagesEXT),
+        reinterpret_cast<PFN_vkVoidFunction>(checkedReleaseSwapchainImagesEXT),
+    },
+    {
         "vkSetHdrMetadataEXT",
         ProcHook::DEVICE,
         ProcHook::EXT_hdr_metadata,
@@ -580,6 +596,8 @@
     if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface;
     if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities;
     if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain;
+    if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1;
+    if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1;
     if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer;
     if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2;
     if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2;
@@ -587,6 +605,7 @@
     if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities;
     if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities;
     if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities;
+    if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd;
     // clang-format on
     return ProcHook::EXTENSION_UNKNOWN;
 }
@@ -666,6 +685,7 @@
     INIT_PROC(true, dev, CreateImage);
     INIT_PROC(true, dev, DestroyImage);
     INIT_PROC(true, dev, AllocateCommandBuffers);
+    INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR);
     INIT_PROC(false, dev, BindImageMemory2);
     INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR);
     INIT_PROC(false, dev, GetDeviceQueue2);
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 2f60086..31ba04b 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -49,6 +49,8 @@
         KHR_surface,
         KHR_surface_protected_capabilities,
         KHR_swapchain,
+        EXT_swapchain_maintenance1,
+        EXT_surface_maintenance1,
         ANDROID_external_memory_android_hardware_buffer,
         KHR_bind_memory2,
         KHR_get_physical_device_properties2,
@@ -56,6 +58,7 @@
         KHR_external_memory_capabilities,
         KHR_external_semaphore_capabilities,
         KHR_external_fence_capabilities,
+        KHR_external_fence_fd,
 
         EXTENSION_CORE_1_0,
         EXTENSION_CORE_1_1,
@@ -118,6 +121,7 @@
     PFN_vkCreateImage CreateImage;
     PFN_vkDestroyImage DestroyImage;
     PFN_vkAllocateCommandBuffers AllocateCommandBuffers;
+    PFN_vkImportFenceFdKHR ImportFenceFdKHR;
     PFN_vkBindImageMemory2 BindImageMemory2;
     PFN_vkBindImageMemory2KHR BindImageMemory2KHR;
     PFN_vkGetDeviceQueue2 GetDeviceQueue2;
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index b03200e..1bff50d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -291,6 +291,9 @@
               release_fence(-1),
               dequeued(false) {}
         VkImage image;
+        // If the image is bound to memory, an sp to the underlying gralloc buffer.
+        // Otherwise, nullptr; the image will be bound to memory as part of
+        // AcquireNextImage.
         android::sp<ANativeWindowBuffer> buffer;
         // The fence is only valid when the buffer is dequeued, and should be
         // -1 any other time. When valid, we own the fd, and must ensure it is
@@ -656,100 +659,40 @@
     VkSurfaceCapabilitiesKHR* capabilities) {
     ATRACE_CALL();
 
-    int err;
-    int width, height;
-    int transform_hint;
-    int max_buffer_count;
-    if (surface == VK_NULL_HANDLE) {
-        const InstanceData& instance_data = GetData(pdev);
-        ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
-        bool surfaceless_enabled =
-            instance_data.hook_extensions.test(surfaceless);
-        if (!surfaceless_enabled) {
-            // It is an error to pass a surface==VK_NULL_HANDLE unless the
-            // VK_GOOGLE_surfaceless_query extension is enabled
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        // Support for VK_GOOGLE_surfaceless_query.  The primary purpose of this
-        // extension for this function is for
-        // VkSurfaceProtectedCapabilitiesKHR::supportsProtected.  The following
-        // four values cannot be known without a surface.  Default values will
-        // be supplied anyway, but cannot be relied upon.
-        width = 0xFFFFFFFF;
-        height = 0xFFFFFFFF;
-        transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
-        capabilities->minImageCount = 0xFFFFFFFF;
-        capabilities->maxImageCount = 0xFFFFFFFF;
+    // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR
+
+    VkPhysicalDeviceSurfaceInfo2KHR info2 = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+        nullptr,
+        surface
+    };
+
+    VkSurfaceCapabilities2KHR caps2 = {
+        VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+        nullptr,
+        {},
+    };
+
+    VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2);
+    *capabilities = caps2.surfaceCapabilities;
+    return result;
+}
+
+// Does the call-twice and VK_INCOMPLETE handling for querying lists
+// of things, where we already have the full set built in a vector.
+template <typename T>
+VkResult CopyWithIncomplete(std::vector<T> const& things,
+        T* callerPtr, uint32_t* callerCount) {
+    VkResult result = VK_SUCCESS;
+    if (callerPtr) {
+        if (things.size() > *callerCount)
+            result = VK_INCOMPLETE;
+        *callerCount = std::min(uint32_t(things.size()), *callerCount);
+        std::copy(things.begin(), things.begin() + *callerCount, callerPtr);
     } else {
-        ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
-
-        err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
-        if (err != android::OK) {
-            ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
-                  strerror(-err), err);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
-        if (err != android::OK) {
-            ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
-                  strerror(-err), err);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-
-        err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
-                            &transform_hint);
-        if (err != android::OK) {
-            ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
-                  strerror(-err), err);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-
-        err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
-                            &max_buffer_count);
-        if (err != android::OK) {
-            ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)",
-                  strerror(-err), err);
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        capabilities->minImageCount = std::min(max_buffer_count, 3);
-        capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+        *callerCount = things.size();
     }
-
-    capabilities->currentExtent =
-        VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
-
-    // TODO(http://b/134182502): Figure out what the max extent should be.
-    capabilities->minImageExtent = VkExtent2D{1, 1};
-    capabilities->maxImageExtent = VkExtent2D{4096, 4096};
-
-    if (capabilities->maxImageExtent.height <
-        capabilities->currentExtent.height) {
-        capabilities->maxImageExtent.height =
-            capabilities->currentExtent.height;
-    }
-
-    if (capabilities->maxImageExtent.width <
-        capabilities->currentExtent.width) {
-        capabilities->maxImageExtent.width = capabilities->currentExtent.width;
-    }
-
-    capabilities->maxImageArrayLayers = 1;
-
-    capabilities->supportedTransforms = kSupportedTransforms;
-    capabilities->currentTransform =
-        TranslateNativeToVulkanTransform(transform_hint);
-
-    // On Android, window composition is a WindowManager property, not something
-    // associated with the bufferqueue. It can't be changed from here.
-    capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
-
-    capabilities->supportedUsageFlags =
-        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
-        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
-        VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
-        VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
-
-    return VK_SUCCESS;
+    return result;
 }
 
 VKAPI_ATTR
@@ -882,21 +825,7 @@
     // Android users.  This includes the ANGLE team (a layered implementation of
     // OpenGL-ES).
 
-    VkResult result = VK_SUCCESS;
-    if (formats) {
-        uint32_t transfer_count = all_formats.size();
-        if (transfer_count > *count) {
-            transfer_count = *count;
-            result = VK_INCOMPLETE;
-        }
-        std::copy(all_formats.begin(), all_formats.begin() + transfer_count,
-                  formats);
-        *count = transfer_count;
-    } else {
-        *count = all_formats.size();
-    }
-
-    return result;
+    return CopyWithIncomplete(all_formats, formats, count);
 }
 
 VKAPI_ATTR
@@ -906,19 +835,134 @@
     VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
     ATRACE_CALL();
 
-    VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR(
-        physicalDevice, pSurfaceInfo->surface,
-        &pSurfaceCapabilities->surfaceCapabilities);
+    auto surface = pSurfaceInfo->surface;
+    auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
 
-    VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities;
-    while (caps->pNext) {
-        caps = reinterpret_cast<VkSurfaceCapabilities2KHR*>(caps->pNext);
+    VkSurfacePresentModeEXT const *pPresentMode = nullptr;
+    for (auto pNext = reinterpret_cast<VkBaseInStructure const *>(pSurfaceInfo->pNext);
+            pNext; pNext = reinterpret_cast<VkBaseInStructure const *>(pNext->pNext)) {
+        switch (pNext->sType) {
+            case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT:
+                pPresentMode = reinterpret_cast<VkSurfacePresentModeEXT const *>(pNext);
+                break;
 
-        switch (caps->sType) {
+            default:
+                break;
+        }
+    }
+
+    int err;
+    int width, height;
+    int transform_hint;
+    int max_buffer_count;
+    if (surface == VK_NULL_HANDLE) {
+        const InstanceData& instance_data = GetData(physicalDevice);
+        ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
+        bool surfaceless_enabled =
+            instance_data.hook_extensions.test(surfaceless);
+        if (!surfaceless_enabled) {
+            // It is an error to pass a surface==VK_NULL_HANDLE unless the
+            // VK_GOOGLE_surfaceless_query extension is enabled
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+        // Support for VK_GOOGLE_surfaceless_query.  The primary purpose of this
+        // extension for this function is for
+        // VkSurfaceProtectedCapabilitiesKHR::supportsProtected.  The following
+        // four values cannot be known without a surface.  Default values will
+        // be supplied anyway, but cannot be relied upon.
+        width = 0xFFFFFFFF;
+        height = 0xFFFFFFFF;
+        transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
+        capabilities->minImageCount = 0xFFFFFFFF;
+        capabilities->maxImageCount = 0xFFFFFFFF;
+    } else {
+        ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
+
+        err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
+        if (err != android::OK) {
+            ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+                  strerror(-err), err);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+        err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
+        if (err != android::OK) {
+            ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+                  strerror(-err), err);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+
+        err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
+                            &transform_hint);
+        if (err != android::OK) {
+            ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
+                  strerror(-err), err);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+
+        err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
+                            &max_buffer_count);
+        if (err != android::OK) {
+            ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)",
+                  strerror(-err), err);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+
+        if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) {
+            capabilities->minImageCount = 1;
+            capabilities->maxImageCount = 1;
+        } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
+            // TODO: use undequeued buffer requirement for more precise bound
+            capabilities->minImageCount = std::min(max_buffer_count, 4);
+            capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+        } else {
+            // TODO: if we're able to, provide better bounds on the number of buffers
+            // for other modes as well.
+            capabilities->minImageCount = std::min(max_buffer_count, 3);
+            capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+        }
+    }
+
+    capabilities->currentExtent =
+        VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+
+    // TODO(http://b/134182502): Figure out what the max extent should be.
+    capabilities->minImageExtent = VkExtent2D{1, 1};
+    capabilities->maxImageExtent = VkExtent2D{4096, 4096};
+
+    if (capabilities->maxImageExtent.height <
+        capabilities->currentExtent.height) {
+        capabilities->maxImageExtent.height =
+            capabilities->currentExtent.height;
+    }
+
+    if (capabilities->maxImageExtent.width <
+        capabilities->currentExtent.width) {
+        capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+    }
+
+    capabilities->maxImageArrayLayers = 1;
+
+    capabilities->supportedTransforms = kSupportedTransforms;
+    capabilities->currentTransform =
+        TranslateNativeToVulkanTransform(transform_hint);
+
+    // On Android, window composition is a WindowManager property, not something
+    // associated with the bufferqueue. It can't be changed from here.
+    capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+
+    capabilities->supportedUsageFlags =
+        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
+        VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+        VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
+    for (auto pNext = reinterpret_cast<VkBaseOutStructure*>(pSurfaceCapabilities->pNext);
+            pNext; pNext = reinterpret_cast<VkBaseOutStructure*>(pNext->pNext)) {
+
+        switch (pNext->sType) {
             case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: {
                 VkSharedPresentSurfaceCapabilitiesKHR* shared_caps =
-                    reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(
-                        caps);
+                    reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(pNext);
                 // Claim same set of usage flags are supported for
                 // shared present modes as for other modes.
                 shared_caps->sharedPresentSupportedUsageFlags =
@@ -928,17 +972,64 @@
 
             case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: {
                 VkSurfaceProtectedCapabilitiesKHR* protected_caps =
-                    reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(caps);
+                    reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(pNext);
                 protected_caps->supportsProtected = VK_TRUE;
             } break;
 
+            case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: {
+                VkSurfacePresentScalingCapabilitiesEXT* scaling_caps =
+                    reinterpret_cast<VkSurfacePresentScalingCapabilitiesEXT*>(pNext);
+                // By default, Android stretches the buffer to fit the window,
+                // without preserving aspect ratio. Other modes are technically possible
+                // but consult with CoGS team before exposing them here!
+                scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT;
+
+                // Since we always scale, we don't support any gravity.
+                scaling_caps->supportedPresentGravityX = 0;
+                scaling_caps->supportedPresentGravityY = 0;
+
+                // Scaled image limits are just the basic image limits
+                scaling_caps->minScaledImageExtent = capabilities->minImageExtent;
+                scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent;
+            } break;
+
+            case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: {
+                VkSurfacePresentModeCompatibilityEXT* mode_caps =
+                    reinterpret_cast<VkSurfacePresentModeCompatibilityEXT*>(pNext);
+
+                ALOG_ASSERT(pPresentMode,
+                        "querying VkSurfacePresentModeCompatibilityEXT "
+                        "requires VkSurfacePresentModeEXT to be provided");
+                std::vector<VkPresentModeKHR> compatibleModes;
+                compatibleModes.push_back(pPresentMode->presentMode);
+
+                switch (pPresentMode->presentMode) {
+                    // Shared modes are both compatible with each other.
+                    case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
+                        compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+                        break;
+                    case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
+                        compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR);
+                        break;
+                    default:
+                        // Other modes are only compatible with themselves.
+                        // TODO: consider whether switching between FIFO and MAILBOX is reasonable
+                        break;
+                }
+
+                // Note: this does not generate VK_INCOMPLETE since we're nested inside
+                // a larger query and there would be no way to determine exactly where it came from.
+                CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes,
+                        &mode_caps->presentModeCount);
+            } break;
+
             default:
                 // Ignore all other extension structs
                 break;
         }
     }
 
-    return result;
+    return VK_SUCCESS;
 }
 
 VKAPI_ATTR
@@ -1097,18 +1188,7 @@
         present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
     }
 
-    uint32_t num_modes = uint32_t(present_modes.size());
-
-    VkResult result = VK_SUCCESS;
-    if (modes) {
-        if (*count < num_modes)
-            result = VK_INCOMPLETE;
-        *count = std::min(*count, num_modes);
-        std::copy_n(present_modes.data(), *count, modes);
-    } else {
-        *count = num_modes;
-    }
-    return result;
+    return CopyWithIncomplete(present_modes, modes, count);
 }
 
 VKAPI_ATTR
@@ -1394,8 +1474,7 @@
     }
 
     VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0;
-    if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
-        create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) {
+    if (IsSharedPresentMode(create_info->presentMode)) {
         swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID;
         err = native_window_set_shared_buffer_mode(window, true);
         if (err != android::OK) {
@@ -1545,8 +1624,6 @@
     Swapchain* swapchain = new (mem)
         Swapchain(surface, num_images, create_info->presentMode,
                   TranslateVulkanToNativeTransform(create_info->preTransform));
-    // -- Dequeue all buffers and create a VkImage for each --
-    // Any failures during or after this must cancel the dequeued buffers.
 
     VkSwapchainImageCreateInfoANDROID swapchain_image_create = {
 #pragma clang diagnostic push
@@ -1563,13 +1640,18 @@
 #pragma clang diagnostic pop
         .pNext = &swapchain_image_create,
     };
+
     VkImageCreateInfo image_create = {
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
-        .pNext = &image_native_buffer,
+        .pNext = nullptr,
         .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
         .imageType = VK_IMAGE_TYPE_2D,
         .format = create_info->imageFormat,
-        .extent = {0, 0, 1},
+        .extent = {
+            create_info->imageExtent.width,
+            create_info->imageExtent.height,
+            1
+        },
         .mipLevels = 1,
         .arrayLayers = 1,
         .samples = VK_SAMPLE_COUNT_1_BIT,
@@ -1580,60 +1662,87 @@
         .pQueueFamilyIndices = create_info->pQueueFamilyIndices,
     };
 
-    for (uint32_t i = 0; i < num_images; i++) {
-        Swapchain::Image& img = swapchain->images[i];
+    // Note: don't do deferred allocation for shared present modes. There's only one buffer
+    // involved so very little benefit.
+    if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) &&
+            !IsSharedPresentMode(create_info->presentMode)) {
+        // Don't want to touch the underlying gralloc buffers yet;
+        // instead just create unbound VkImages which will later be bound to memory inside
+        // AcquireNextImage.
+        VkImageSwapchainCreateInfoKHR image_swapchain_create = {
+            .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR,
+            .pNext = nullptr,
+            .swapchain = HandleFromSwapchain(swapchain),
+        };
+        image_create.pNext = &image_swapchain_create;
 
-        ANativeWindowBuffer* buffer;
-        err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
-        if (err != android::OK) {
-            ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
-            switch (-err) {
-                case ENOMEM:
-                    result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
-                    break;
-                default:
-                    result = VK_ERROR_SURFACE_LOST_KHR;
-                    break;
+        for (uint32_t i = 0; i < num_images; i++) {
+            Swapchain::Image& img = swapchain->images[i];
+            img.buffer = nullptr;
+            img.dequeued = false;
+
+            result = dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+            if (result != VK_SUCCESS) {
+                ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result);
+                break;
             }
-            break;
         }
-        img.buffer = buffer;
-        img.dequeued = true;
+    } else {
+        // -- Dequeue all buffers and create a VkImage for each --
+        // Any failures during or after this must cancel the dequeued buffers.
 
-        image_create.extent =
-            VkExtent3D{static_cast<uint32_t>(img.buffer->width),
-                       static_cast<uint32_t>(img.buffer->height),
-                       1};
-        image_native_buffer.handle = img.buffer->handle;
-        image_native_buffer.stride = img.buffer->stride;
-        image_native_buffer.format = img.buffer->format;
-        image_native_buffer.usage = int(img.buffer->usage);
-        android_convertGralloc0To1Usage(int(img.buffer->usage),
-            &image_native_buffer.usage2.producer,
-            &image_native_buffer.usage2.consumer);
-        image_native_buffer.usage3 = img.buffer->usage;
+        for (uint32_t i = 0; i < num_images; i++) {
+            Swapchain::Image& img = swapchain->images[i];
 
-        ATRACE_BEGIN("CreateImage");
-        result =
-            dispatch.CreateImage(device, &image_create, nullptr, &img.image);
-        ATRACE_END();
-        if (result != VK_SUCCESS) {
-            ALOGD("vkCreateImage w/ native buffer failed: %u", result);
-            break;
+            ANativeWindowBuffer* buffer;
+            err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
+            if (err != android::OK) {
+                ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
+                switch (-err) {
+                    case ENOMEM:
+                        result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
+                        break;
+                    default:
+                        result = VK_ERROR_SURFACE_LOST_KHR;
+                        break;
+                }
+                break;
+            }
+            img.buffer = buffer;
+            img.dequeued = true;
+
+            image_native_buffer.handle = img.buffer->handle;
+            image_native_buffer.stride = img.buffer->stride;
+            image_native_buffer.format = img.buffer->format;
+            image_native_buffer.usage = int(img.buffer->usage);
+            android_convertGralloc0To1Usage(int(img.buffer->usage),
+                &image_native_buffer.usage2.producer,
+                &image_native_buffer.usage2.consumer);
+            image_native_buffer.usage3 = img.buffer->usage;
+            image_create.pNext = &image_native_buffer;
+
+            ATRACE_BEGIN("CreateImage");
+            result =
+                dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+            ATRACE_END();
+            if (result != VK_SUCCESS) {
+                ALOGD("vkCreateImage w/ native buffer failed: %u", result);
+                break;
+            }
         }
-    }
 
-    // -- Cancel all buffers, returning them to the queue --
-    // If an error occurred before, also destroy the VkImage and release the
-    // buffer reference. Otherwise, we retain a strong reference to the buffer.
-    for (uint32_t i = 0; i < num_images; i++) {
-        Swapchain::Image& img = swapchain->images[i];
-        if (img.dequeued) {
-            if (!swapchain->shared) {
-                window->cancelBuffer(window, img.buffer.get(),
-                                     img.dequeue_fence);
-                img.dequeue_fence = -1;
-                img.dequeued = false;
+        // -- Cancel all buffers, returning them to the queue --
+        // If an error occurred before, also destroy the VkImage and release the
+        // buffer reference. Otherwise, we retain a strong reference to the buffer.
+        for (uint32_t i = 0; i < num_images; i++) {
+            Swapchain::Image& img = swapchain->images[i];
+            if (img.dequeued) {
+                if (!swapchain->shared) {
+                    window->cancelBuffer(window, img.buffer.get(),
+                                         img.dequeue_fence);
+                    img.dequeue_fence = -1;
+                    img.dequeued = false;
+                }
             }
         }
     }
@@ -1756,6 +1865,64 @@
             break;
         }
     }
+
+    // If this is a deferred alloc swapchain, this may be the first time we've
+    // seen a particular buffer. If so, there should be an empty slot. Find it,
+    // and bind the gralloc buffer to the VkImage for that slot. If there is no
+    // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains
+    // will also take this path, but will never have an empty slot since we
+    // populated them all upfront.
+    if (idx == swapchain.num_images) {
+        for (idx = 0; idx < swapchain.num_images; idx++) {
+            if (!swapchain.images[idx].buffer) {
+                // Note: this structure is technically required for
+                // Vulkan correctness, even though the driver is probably going
+                // to use everything from the VkNativeBufferANDROID below.
+                // This is kindof silly, but it's how we did the ANB
+                // side of VK_KHR_swapchain v69, so we're stuck with it unless
+                // we want to go tinkering with the ANB spec some more.
+                VkBindImageMemorySwapchainInfoKHR bimsi = {
+                    .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR,
+                    .pNext = nullptr,
+                    .swapchain = swapchain_handle,
+                    .imageIndex = idx,
+                };
+                VkNativeBufferANDROID nb = {
+                    .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
+                    .pNext = &bimsi,
+                    .handle = buffer->handle,
+                    .stride = buffer->stride,
+                    .format = buffer->format,
+                    .usage = int(buffer->usage),
+                };
+                VkBindImageMemoryInfo bimi = {
+                    .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
+                    .pNext = &nb,
+                    .image = swapchain.images[idx].image,
+                    .memory = VK_NULL_HANDLE,
+                    .memoryOffset = 0,
+                };
+                result = GetData(device).driver.BindImageMemory2(device, 1, &bimi);
+                if (result != VK_SUCCESS) {
+                    // This shouldn't really happen. If it does, something is probably
+                    // unrecoverably wrong with the swapchain and its images. Cancel
+                    // the buffer and declare the swapchain broken.
+                    ALOGE("failed to do deferred gralloc buffer bind");
+                    window->cancelBuffer(window, buffer, fence_fd);
+                    return VK_ERROR_OUT_OF_DATE_KHR;
+                }
+
+                swapchain.images[idx].dequeued = true;
+                swapchain.images[idx].dequeue_fence = fence_fd;
+                swapchain.images[idx].buffer = buffer;
+                break;
+            }
+        }
+    }
+
+    // The buffer doesn't match any slot. This shouldn't normally happen, but is
+    // possible if the bufferqueue is reconfigured behind libvulkan's back. If this
+    // happens, just declare the swapchain to be broken and the app will recreate it.
     if (idx == swapchain.num_images) {
         ALOGE("dequeueBuffer returned unrecognized buffer");
         window->cancelBuffer(window, buffer, fence_fd);
@@ -1881,12 +2048,32 @@
     }
 }
 
+// EXT_swapchain_maintenance1 present mode change
+static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) {
+    // There is no dynamic switching between non-shared present modes.
+    // All we support is switching between demand and continuous refresh.
+    if (!IsSharedPresentMode(mode))
+        return true;
+
+    int err = native_window_set_auto_refresh(window,
+            mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+    if (err != android::OK) {
+        ALOGE("native_window_set_auto_refresh() failed: %s (%d)",
+              strerror(-err), err);
+        return false;
+    }
+
+    return true;
+}
+
 static VkResult PresentOneSwapchain(
         VkQueue queue,
         Swapchain& swapchain,
         uint32_t imageIndex,
         const VkPresentRegionKHR *pRegion,
         const VkPresentTimeGOOGLE *pTime,
+        VkFence presentFence,
+        const VkPresentModeKHR *pPresentMode,
         uint32_t waitSemaphoreCount,
         const VkSemaphore *pWaitSemaphores) {
 
@@ -1917,12 +2104,33 @@
         ANativeWindow* window = swapchain.surface.window.get();
         if (swapchain_result == VK_SUCCESS) {
 
+            if (presentFence != VK_NULL_HANDLE) {
+                int fence_copy = fence < 0 ? -1 : dup(fence);
+                VkImportFenceFdInfoKHR iffi = {
+                    VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR,
+                    nullptr,
+                    presentFence,
+                    VK_FENCE_IMPORT_TEMPORARY_BIT,
+                    VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT,
+                    fence_copy,
+                };
+                if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) {
+                    // ImportFenceFdKHR takes ownership only if it succeeds
+                    close(fence_copy);
+                }
+            }
+
             if (pRegion) {
                 SetSwapchainSurfaceDamage(window, pRegion);
             }
             if (pTime) {
                 SetSwapchainFrameTimestamp(swapchain, pTime);
             }
+            if (pPresentMode) {
+                if (!SetSwapchainPresentMode(window, *pPresentMode))
+                    swapchain_result = WorstPresentResult(swapchain_result,
+                        VK_ERROR_SURFACE_LOST_KHR);
+            }
 
             err = window->queueBuffer(window, img.buffer.get(), fence);
             // queueBuffer always closes fence, even on error
@@ -2003,6 +2211,9 @@
     // Look at the pNext chain for supported extension structs:
     const VkPresentRegionsKHR* present_regions = nullptr;
     const VkPresentTimesInfoGOOGLE* present_times = nullptr;
+    const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr;
+    const VkSwapchainPresentModeInfoEXT* present_modes = nullptr;
+
     const VkPresentRegionsKHR* next =
         reinterpret_cast<const VkPresentRegionsKHR*>(present_info->pNext);
     while (next) {
@@ -2014,6 +2225,14 @@
                 present_times =
                     reinterpret_cast<const VkPresentTimesInfoGOOGLE*>(next);
                 break;
+            case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT:
+                present_fences =
+                    reinterpret_cast<const VkSwapchainPresentFenceInfoEXT*>(next);
+                break;
+            case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT:
+                present_modes =
+                    reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(next);
+                break;
             default:
                 ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x",
                       next->sType);
@@ -2029,6 +2248,15 @@
                  present_times->swapchainCount != present_info->swapchainCount,
              "VkPresentTimesInfoGOOGLE::swapchainCount != "
              "VkPresentInfo::swapchainCount");
+    ALOGV_IF(present_fences &&
+             present_fences->swapchainCount != present_info->swapchainCount,
+             "VkSwapchainPresentFenceInfoEXT::swapchainCount != "
+             "VkPresentInfo::swapchainCount");
+    ALOGV_IF(present_modes &&
+             present_modes->swapchainCount != present_info->swapchainCount,
+             "VkSwapchainPresentModeInfoEXT::swapchainCount != "
+             "VkPresentInfo::swapchainCount");
+
     const VkPresentRegionKHR* regions =
         (present_regions) ? present_regions->pRegions : nullptr;
     const VkPresentTimeGOOGLE* times =
@@ -2044,6 +2272,8 @@
             present_info->pImageIndices[sc],
             (regions && !swapchain.mailbox_mode) ? &regions[sc] : nullptr,
             times ? &times[sc] : nullptr,
+            present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE,
+            present_modes ? &present_modes->pPresentModes[sc] : nullptr,
             present_info->waitSemaphoreCount,
             present_info->pWaitSemaphores);
 
@@ -2268,5 +2498,35 @@
         out_bind_infos.empty() ? pBindInfos : out_bind_infos.data());
 }
 
+VKAPI_ATTR
+VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/,
+                                   const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+    ATRACE_CALL();
+
+    Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain);
+    ANativeWindow* window = swapchain.surface.window.get();
+
+    // If in shared present mode, don't actually release the image back to the BQ.
+    // Both sides share it forever.
+    if (swapchain.shared)
+        return VK_SUCCESS;
+
+    for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) {
+        Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]];
+        window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence);
+
+        // cancelBuffer has taken ownership of the dequeue fence
+        img.dequeue_fence = -1;
+        // if we're still holding a release fence, get rid of it now
+        if (img.release_fence >= 0) {
+           close(img.release_fence);
+           img.release_fence = -1;
+        }
+        img.dequeued = false;
+    }
+
+    return VK_SUCCESS;
+}
+
 }  // namespace driver
 }  // namespace vulkan
diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h
index 4912ef1..280fe9b 100644
--- a/vulkan/libvulkan/swapchain.h
+++ b/vulkan/libvulkan/swapchain.h
@@ -46,6 +46,7 @@
 VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats);
 VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
 VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
+VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo);
 // clang-format on
 
 }  // namespace driver
diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py
index af56764..78b550c 100644
--- a/vulkan/scripts/driver_generator.py
+++ b/vulkan/scripts/driver_generator.py
@@ -35,6 +35,8 @@
     'VK_KHR_surface',
     'VK_KHR_surface_protected_capabilities',
     'VK_KHR_swapchain',
+    'VK_EXT_swapchain_maintenance1',
+    'VK_EXT_surface_maintenance1',
 ]
 
 # Extensions known to vulkan::driver level.
@@ -46,6 +48,7 @@
     'VK_KHR_external_memory_capabilities',
     'VK_KHR_external_semaphore_capabilities',
     'VK_KHR_external_fence_capabilities',
+    'VK_KHR_external_fence_fd',
 ]
 
 # Functions needed at vulkan::driver level.
@@ -112,6 +115,9 @@
     # For promoted VK_KHR_external_fence_capabilities
     'vkGetPhysicalDeviceExternalFenceProperties',
     'vkGetPhysicalDeviceExternalFencePropertiesKHR',
+
+    # VK_KHR_swapchain_maintenance1 requirement
+    'vkImportFenceFdKHR',
 ]
 
 # Functions intercepted at vulkan::driver level.