Merge "Copy tilt/orientation from last real event." into main
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 589df9a..ef0ae4f 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -54,6 +54,11 @@
 // Another arbitrary value a binder count needs to drop below before another callback will be called
 uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000;
 
+std::atomic<uint32_t> BpBinder::sBinderProxyCount(0);
+std::atomic<uint32_t> BpBinder::sBinderProxyCountWarned(0);
+
+static constexpr uint32_t kBinderProxyCountWarnInterval = 5000;
+
 // Log any transactions for which the data exceeds this size
 #define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024)
 
@@ -193,6 +198,18 @@
         }
         sTrackingMap[trackedUid]++;
     }
+    uint32_t numProxies = sBinderProxyCount.fetch_add(1, std::memory_order_relaxed);
+    uint32_t numLastWarned = sBinderProxyCountWarned.load(std::memory_order_relaxed);
+    uint32_t numNextWarn = numLastWarned + kBinderProxyCountWarnInterval;
+    if (numProxies >= numNextWarn) {
+        // Multiple threads can get here, make sure only one of them gets to
+        // update the warn counter.
+        if (sBinderProxyCountWarned.compare_exchange_strong(numLastWarned,
+                                                            numNextWarn,
+                                                            std::memory_order_relaxed)) {
+            ALOGW("Unexpectedly many live BinderProxies: %d\n", numProxies);
+        }
+    }
     return sp<BpBinder>::make(BinderHandle{handle}, trackedUid);
 }
 
@@ -604,6 +621,7 @@
             }
         }
     }
+    --sBinderProxyCount;
 
     if (ipc) {
         ipc->expungeHandle(binderHandle(), this);
@@ -688,6 +706,11 @@
     return 0;
 }
 
+uint32_t BpBinder::getBinderProxyCount()
+{
+    return sBinderProxyCount.load();
+}
+
 void BpBinder::getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts)
 {
     AutoMutex _l(sTrackingLock);
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 5496d61..a5c6094 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -87,6 +87,7 @@
     static void         setCountByUidEnabled(bool enable);
     static void         setLimitCallback(binder_proxy_limit_callback cb);
     static void         setBinderProxyCountWatermarks(int high, int low);
+    static uint32_t     getBinderProxyCount();
 
     std::optional<int32_t> getDebugBinderHandle() const;
 
@@ -208,6 +209,8 @@
     static uint32_t                             sBinderProxyCountLowWatermark;
     static bool                                 sBinderProxyThrottleCreate;
     static std::unordered_map<int32_t,uint32_t> sLastLimitCallbackMap;
+    static std::atomic<uint32_t>                sBinderProxyCount;
+    static std::atomic<uint32_t>                sBinderProxyCountWarned;
 };
 
 } // namespace android
diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h
index 76c7aac..4786c89 100644
--- a/libs/binder/ndk/include_ndk/android/binder_status.h
+++ b/libs/binder/ndk/include_ndk/android/binder_status.h
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include <assert.h>
 #include <errno.h>
 #include <stdbool.h>
 #include <stdint.h>
diff --git a/libs/binder/ndk/tests/Android.bp b/libs/binder/ndk/tests/Android.bp
index 8ee396e..8fb755c 100644
--- a/libs/binder/ndk/tests/Android.bp
+++ b/libs/binder/ndk/tests/Android.bp
@@ -80,6 +80,28 @@
     require_root: true,
 }
 
+cc_test_host {
+    name: "libbinder_ndk_unit_test_host",
+    defaults: ["test_libbinder_ndk_defaults"],
+    srcs: ["libbinder_ndk_unit_test_host.cpp"],
+    test_suites: [
+        "general-tests",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    static_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libbinder",
+        "libcutils",
+        "libfakeservicemanager",
+        "libgmock",
+        "liblog",
+        "libutils",
+    ],
+}
+
 cc_test {
     name: "binderVendorDoubleLoadTest",
     vendor: true,
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 25b8e97..15708ca 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -39,7 +39,6 @@
 #include <condition_variable>
 #include <iostream>
 #include <mutex>
-#include <optional>
 #include <thread>
 
 #include "android/binder_ibinder.h"
@@ -433,21 +432,6 @@
     EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
 }
 
-TEST(NdkBinder, IsUpdatable) {
-    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.light.ILights/default");
-    EXPECT_EQ(isUpdatable, false);
-}
-
-TEST(NdkBinder, GetUpdatableViaApex) {
-    std::optional<std::string> updatableViaApex;
-    AServiceManager_getUpdatableApexName(
-            "android.hardware.light.ILights/default", &updatableViaApex,
-            [](const char* apexName, void* context) {
-                *static_cast<std::optional<std::string>*>(context) = apexName;
-            });
-    EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex;
-}
-
 // This is too slow
 TEST(NdkBinder, CheckLazyServiceShutDown) {
     ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService));
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
new file mode 100644
index 0000000..0a3021d
--- /dev/null
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+#include <optional>
+
+#include "fakeservicemanager/FakeServiceManager.h"
+
+using android::FakeServiceManager;
+using android::setDefaultServiceManager;
+using android::sp;
+using android::String16;
+using android::String8;
+using testing::_;
+using testing::Eq;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Optional;
+using testing::Return;
+
+struct MockServiceManager : FakeServiceManager {
+    MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+};
+
+struct AServiceManager : testing::Test {
+    static sp<MockServiceManager> mockSM;
+
+    static void InitMock() {
+        mockSM = new NiceMock<MockServiceManager>;
+        setDefaultServiceManager(mockSM);
+    }
+
+    void TearDown() override { Mock::VerifyAndClear(mockSM.get()); }
+
+    void ExpectUpdatableViaApexReturns(std::optional<String16> apexName) {
+        EXPECT_CALL(*mockSM, updatableViaApex(_)).WillRepeatedly(Return(apexName));
+    }
+};
+
+sp<MockServiceManager> AServiceManager::mockSM;
+
+TEST_F(AServiceManager, isUpdatableViaApex) {
+    auto apexFoo = String16("com.android.hardware.foo");
+    ExpectUpdatableViaApexReturns(apexFoo);
+
+    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+    EXPECT_EQ(isUpdatable, true);
+}
+
+TEST_F(AServiceManager, isUpdatableViaApex_Not) {
+    ExpectUpdatableViaApexReturns(std::nullopt);
+
+    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+    EXPECT_EQ(isUpdatable, false);
+}
+
+void getUpdatableApexNameCallback(const char* apexName, void* context) {
+    *(static_cast<std::optional<std::string>*>(context)) = apexName;
+}
+
+TEST_F(AServiceManager, getUpdatableApexName) {
+    auto apexFoo = String16("com.android.hardware.foo");
+    ExpectUpdatableViaApexReturns(apexFoo);
+
+    std::optional<std::string> result;
+    AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+                                         getUpdatableApexNameCallback);
+    EXPECT_THAT(result, Optional(std::string(String8(apexFoo))));
+}
+
+TEST_F(AServiceManager, getUpdatableApexName_Null) {
+    ExpectUpdatableViaApexReturns(std::nullopt);
+
+    std::optional<std::string> result;
+    AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+                                         getUpdatableApexNameCallback);
+    EXPECT_THAT(result, Eq(std::nullopt));
+}
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    AServiceManager::InitMock();
+    return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
index 09cb216..b80ac53 100644
--- a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
@@ -16,6 +16,7 @@
 
 #include <commonFuzzHelpers.h>
 #include <fuzzer/FuzzedDataProvider.h>
+#include <functional>
 #include <string>
 #include <vector>
 #include "BufferedTextOutput.h"
diff --git a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
index f835997..9df5632 100644
--- a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
+++ b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
@@ -20,6 +20,7 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include <android-base/unique_fd.h>
 #include <cputimeinstate.h>
+#include <functional>
 
 using namespace android::bpf;
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 9847c05..613721e 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -85,6 +85,7 @@
         changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+        frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
         isTrustedOverlay(false),
@@ -161,6 +162,7 @@
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
     SAFE_PARCEL(output.writeByte, frameRateCategory);
+    SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeBool, autoRefresh);
     SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -294,6 +296,7 @@
     SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
     SAFE_PARCEL(input.readByte, &frameRateCategory);
+    SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -667,6 +670,10 @@
         what |= eFrameRateCategoryChanged;
         frameRateCategory = other.frameRateCategory;
     }
+    if (other.what & eFrameRateSelectionStrategyChanged) {
+        what |= eFrameRateSelectionStrategyChanged;
+        frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+    }
     if (other.what & eFixedTransformHintChanged) {
         what |= eFixedTransformHintChanged;
         fixedTransformHint = other.fixedTransformHint;
@@ -778,6 +785,7 @@
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
                 changeFrameRateStrategy);
     CHECK_DIFF(diff, eFrameRateCategoryChanged, other, frameRateCategory);
+    CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
     CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index e0882ac..038764b 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2105,6 +2105,19 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+                                                                  int8_t strategy) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+    s->frameRateSelectionStrategy = strategy;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
         const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 102a3c1..4371007 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -197,7 +197,7 @@
         eInputInfoChanged = 0x40000000,
         eCornerRadiusChanged = 0x80000000,
         eDestinationFrameChanged = 0x1'00000000,
-        /* unused = 0x2'00000000, */
+        eFrameRateSelectionStrategyChanged = 0x2'00000000,
         eBackgroundColorChanged = 0x4'00000000,
         eMetadataChanged = 0x8'00000000,
         eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -268,6 +268,7 @@
             layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
             layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+            layer_state_t::eFrameRateSelectionStrategyChanged |
             layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
@@ -361,6 +362,9 @@
     // Frame rate category to suggest what frame rate range a surface should run.
     int8_t frameRateCategory;
 
+    // Strategy of the layer for frame rate selection.
+    int8_t frameRateSelectionStrategy;
+
     // Set by window manager indicating the layer and all its children are
     // in a different orientation than the display. The hint suggests that
     // the graphic producers should receive a transform hint as if the
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 6fef5d2..42e3c16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -687,6 +687,8 @@
 
         Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category);
 
+        Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index ee961f0..a0ec6ad 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -20,6 +20,7 @@
 #include <input/InputDevice.h>
 #include <input/KeyLayoutMap.h>
 #include <input/Keyboard.h>
+#include <linux/uinput.h>
 #include "android-base/file.h"
 
 namespace android {
@@ -97,7 +98,7 @@
     ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) {
     std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
     std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm";
     std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm";
@@ -133,14 +134,33 @@
     ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) {
+    std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
+    base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay =
+            KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY);
+    ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath;
+
+    // Apply the French overlay
+    mKeyMap.keyCharacterMap->combine(*frenchOverlay->get());
+
+    // Check if mapping for key_Q is correct
+    int32_t outKeyCode;
+    status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode);
+    ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath;
+    ASSERT_EQ(outKeyCode, AKEYCODE_A);
+
+    mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode);
+    ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index e158f01..b068f48 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1096,6 +1096,26 @@
     ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
 };
 
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+    /**
+     * Default value. The layer uses its own frame rate specifications, assuming it has any
+     * specifications, instead of its parent's.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 0,
+
+    /**
+     * The layer's frame rate specifications will propagate to and override those of its descendant
+     * layers.
+     * The layer with this strategy has the ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF
+     * behavior for itself.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+};
+
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
                                         int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index e253ad5..cc1d12b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,6 +24,7 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
 #include <gui/TraceUtils.h>
@@ -338,7 +339,8 @@
     } else {
         ATRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = grContext->submit(requireSync);
+    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
+                                                   GrSyncCpu::kNo);
     ATRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 6ecc6ab..17f263d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -681,7 +681,7 @@
         flushInfo.fFinishedContext = destroySemaphoreInfo;
     }
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
-    grContext->submit(false /* no cpu sync */);
+    grContext->submit(GrSyncCpu::kNo);
     int drawFenceFd = -1;
     if (semaphore != VK_NULL_HANDLE) {
         if (GrSemaphoresSubmitted::kYes == submitted) {
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index cb369a8..fa8f548 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -269,6 +269,11 @@
     return msg;
 }
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry) {
+    out << motionEntry.getDescription();
+    return out;
+}
+
 // --- SensorEntry ---
 
 SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 8dc2a2a..dd4aab8 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -24,6 +24,7 @@
 #include <stdint.h>
 #include <utils/Timers.h>
 #include <functional>
+#include <ostream>
 #include <string>
 
 namespace android::inputdispatcher {
@@ -189,6 +190,8 @@
     ~MotionEntry() override;
 };
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry);
+
 struct SensorEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index d98641e..b3be89e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -668,7 +668,15 @@
         } else {
             // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
             if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
-                LOG(FATAL) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
+                android::base::LogSeverity severity = android::base::LogSeverity::FATAL;
+                if (entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) {
+                    // The Accessibility injected touch exploration event stream
+                    // has known inconsistencies, so log ERROR instead of
+                    // crashing the device with FATAL.
+                    // TODO(b/299977100): Move a11y severity back to FATAL.
+                    severity = android::base::LogSeverity::ERROR;
+                }
+                LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
             }
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
         }
@@ -706,6 +714,40 @@
     });
 }
 
+/**
+ * In general, touch should be always split between windows. Some exceptions:
+ * 1. Don't split touch if all of the below is true:
+ *     (a) we have an active pointer down *and*
+ *     (b) a new pointer is going down that's from the same device *and*
+ *     (c) the window that's receiving the current pointer does not support split touch.
+ * 2. Don't split mouse events
+ */
+bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) {
+    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
+        // We should never split mouse events
+        return false;
+    }
+    for (const TouchedWindow& touchedWindow : touchState.windows) {
+        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
+            // Spy windows should not affect whether or not touch is split.
+            continue;
+        }
+        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
+            continue;
+        }
+        if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
+            // Wallpaper window should not affect whether or not touch is split
+            continue;
+        }
+
+        if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -2210,40 +2252,6 @@
     return responsiveMonitors;
 }
 
-/**
- * In general, touch should be always split between windows. Some exceptions:
- * 1. Don't split touch is if we have an active pointer down, and a new pointer is going down that's
- *    from the same device, *and* the window that's receiving the current pointer does not support
- *    split touch.
- * 2. Don't split mouse events
- */
-bool InputDispatcher::shouldSplitTouch(const TouchState& touchState,
-                                       const MotionEntry& entry) const {
-    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
-        // We should never split mouse events
-        return false;
-    }
-    for (const TouchedWindow& touchedWindow : touchState.windows) {
-        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
-            // Spy windows should not affect whether or not touch is split.
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
-            // Wallpaper window should not affect whether or not touch is split
-            continue;
-        }
-
-        if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
         nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
         InputEventInjectionResult& outInjectionResult) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 512bc4f..4d145c6 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -522,7 +522,6 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
-    bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) const;
     int32_t getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
             nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 4221e42..9dcf615 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -288,4 +288,9 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state) {
+    out << state.dump();
+    return out;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 39e63e5..f016936 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "TouchedWindow.h"
 
@@ -79,5 +80,7 @@
     std::string dump() const;
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 9807a6d..ff4b425 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -256,5 +256,10 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
+    out << window.dump();
+    return out;
+}
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 0a38f9f..3f760c0 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -20,6 +20,7 @@
 #include <input/Input.h>
 #include <utils/BitSet.h>
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "InputTarget.h"
 
@@ -92,5 +93,7 @@
     static std::string deviceStateToString(const TouchedWindow::DeviceState& state);
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index b565454..90bd7c9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -130,7 +130,10 @@
 TouchInputMapper::~TouchInputMapper() {}
 
 uint32_t TouchInputMapper::getSources() const {
-    return mSource;
+    // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified
+    // by the external stylus state. That's why we don't add it directly to mSource during
+    // configuration.
+    return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0);
 }
 
 void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
@@ -932,9 +935,6 @@
         if (hasStylus()) {
             mSource |= AINPUT_SOURCE_STYLUS;
         }
-        if (hasExternalStylus()) {
-            mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
-        }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
         mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
         mDeviceMode = DeviceMode::NAVIGATION;
@@ -1664,6 +1664,10 @@
                                 mSource, mViewport.displayId, policyFlags,
                                 mLastCookedState.buttonState, mCurrentCookedState.buttonState);
 
+    if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+        mCurrentStreamModifiedByExternalStylus = false;
+    }
+
     // Clear some transient state.
     mCurrentRawState.rawVScroll = 0;
     mCurrentRawState.rawHScroll = 0;
@@ -1715,6 +1719,10 @@
 
         mExternalStylusButtonsApplied |= pressedButtons;
         mExternalStylusButtonsApplied &= ~releasedButtons;
+
+        if (mExternalStylusButtonsApplied != 0 || releasedButtons != 0) {
+            mCurrentStreamModifiedByExternalStylus = true;
+        }
     }
 }
 
@@ -1725,6 +1733,8 @@
         return;
     }
 
+    mCurrentStreamModifiedByExternalStylus = true;
+
     float pressure = lastPointerData.isTouching(*mFusedStylusPointerId)
             ? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId)
                       .getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)
@@ -3821,6 +3831,9 @@
             ALOG_ASSERT(false);
         }
     }
+    if (mCurrentStreamModifiedByExternalStylus) {
+        source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
+    }
 
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index c5dfb00..bd9371d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -357,6 +357,8 @@
     bool mExternalStylusDataPending;
     // A subset of the buttons in mCurrentRawState that came from an external stylus.
     int32_t mExternalStylusButtonsApplied{0};
+    // True if the current cooked pointer data was modified due to the state of an external stylus.
+    bool mCurrentStreamModifiedByExternalStylus{false};
 
     // True if we sent a HOVER_ENTER event.
     bool mSentHoverEnter{false};
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index dc281a3..dd003a6 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -3349,6 +3349,31 @@
 }
 
 /**
+ * Test that invalid HOVER events sent by accessibility do not cause a fatal crash.
+ */
+TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 1200, 800));
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    MotionEventBuilder hoverEnterBuilder =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                    .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+}
+
+/**
  * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
  */
 TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index bce0937..6539593 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -91,6 +91,9 @@
 static constexpr int32_t ACTION_POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
+static constexpr uint32_t STYLUS_FUSION_SOURCE =
+        AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
+
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
 static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
 // Maximum smoothing time delta so that we don't generate events too far into the future.
@@ -2308,6 +2311,22 @@
 // ongoing stylus gesture that is being emitted by the touchscreen.
 using ExternalStylusIntegrationTest = BaseTouchIntegrationTest;
 
+TEST_F(ExternalStylusIntegrationTest, ExternalStylusConnectionChangesTouchscreenSource) {
+    // Create an external stylus capable of reporting pressure data that
+    // should be fused with a touch pointer.
+    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+            createUinputDevice<UinputExternalStylusWithPressure>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    // Connecting an external stylus changes the source of the touchscreen.
+    const auto deviceInfo = findDeviceByName(mDevice->getName());
+    ASSERT_TRUE(deviceInfo);
+    ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
+}
+
 TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
@@ -2337,17 +2356,17 @@
     mDevice->sendDown(centerPoint);
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::STYLUS),
+                  WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+                  WithPressure(100.f / RAW_PRESSURE_MAX))));
 
     // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
     // event with the updated pressure.
     stylus->setPressure(200);
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithToolType(ToolType::STYLUS),
+                  WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+                  WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2392,8 +2411,8 @@
     // it shows up as a finger pointer.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId),
-                  WithPressure(1.f))));
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f))));
 
     // Change the pressure on the external stylus. Since the pressure was not present at the start
     // of the gesture, it is ignored for now.
@@ -2405,6 +2424,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(ToolType::FINGER))));
 
     // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
@@ -2413,9 +2433,9 @@
     mDevice->sendDown(centerPoint);
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(STYLUS_FUSION_SOURCE),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId),
+                  WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2447,14 +2467,15 @@
             std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(
-            mTestListener
-                    ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                                        WithToolType(
-                                                                ToolType::FINGER),
-                                                        WithButtonState(0),
-                                                        WithDeviceId(touchscreenId),
-                                                        WithPressure(1.f)),
-                                                  waitUntil));
+            mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction(
+                                                                     AMOTION_EVENT_ACTION_DOWN),
+                                                             WithToolType(ToolType::FINGER),
+                                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+                                                                        AINPUT_SOURCE_STYLUS),
+                                                             WithButtonState(0),
+                                                             WithDeviceId(touchscreenId),
+                                                             WithPressure(1.f)),
+                                                       waitUntil));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -7567,12 +7588,10 @@
 
 protected:
     StylusState mStylusState{};
-    static constexpr uint32_t EXPECTED_SOURCE =
-            AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
 
     void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+                AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
         // The first pointer is withheld.
         processDown(mapper, 100, 200);
@@ -7606,7 +7625,7 @@
         processUp(mapper);
         processSync(mapper);
         ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
                       WithToolType(ToolType::STYLUS))));
 
         mStylusState.pressure = 0.f;
@@ -7616,8 +7635,10 @@
     }
 
     void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+        // When stylus fusion is not successful, events should be reported with the original source.
+        // In this case, it is from a touchscreen.
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER));
+                AllOf(WithSource(AINPUT_SOURCE_TOUCHSCREEN), WithToolType(ToolType::FINGER));
 
         // The first pointer is withheld when an external stylus is connected,
         // and a timeout is requested.
@@ -7657,7 +7678,7 @@
 
 TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources());
+    ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources());
 }
 
 TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
@@ -7674,8 +7695,7 @@
 // before the touch is reported by the touchscreen.
 TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     // The external stylus reports pressure first. It is ignored for now.
     mStylusState.pressure = 1.f;
@@ -7717,8 +7737,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     mStylusState.pressure = 0.8f;
     processExternalStylusState(mapper);
@@ -7779,7 +7798,7 @@
     processUp(mapper);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
                   WithToolType(ToolType::STYLUS))));
 
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
@@ -7788,7 +7807,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto source = WithSource(EXPECTED_SOURCE);
+    auto source = WithSource(STYLUS_FUSION_SOURCE);
 
     mStylusState.pressure = 1.f;
     mStylusState.toolType = ToolType::ERASER;
@@ -7841,8 +7860,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 22db247..fe56969 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -844,10 +844,16 @@
 
 bool OutputLayer::needsFiltering() const {
     const auto& state = getState();
-    const auto& displayFrame = state.displayFrame;
     const auto& sourceCrop = state.sourceCrop;
-    return sourceCrop.getHeight() != displayFrame.getHeight() ||
-            sourceCrop.getWidth() != displayFrame.getWidth();
+    auto displayFrameWidth = static_cast<float>(state.displayFrame.getWidth());
+    auto displayFrameHeight = static_cast<float>(state.displayFrame.getHeight());
+
+    if (state.bufferTransform & HAL_TRANSFORM_ROT_90) {
+        std::swap(displayFrameWidth, displayFrameHeight);
+    }
+
+    return sourceCrop.getHeight() != displayFrameHeight ||
+            sourceCrop.getWidth() != displayFrameWidth;
 }
 
 std::optional<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionSettings() const {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 9039d16..630906a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -1614,5 +1614,20 @@
     EXPECT_TRUE(mOutputLayer.needsFiltering());
 }
 
+TEST_F(OutputLayerTest, needsFilteringReturnsFalseIfRotatedDisplaySizeSameAsSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+    mOutputLayer.editState().bufferTransform = Hwc2::Transform::ROT_90;
+
+    EXPECT_FALSE(mOutputLayer.needsFiltering());
+}
+
+TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfRotatedDisplaySizeDiffersFromSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+
+    EXPECT_TRUE(mOutputLayer.needsFiltering());
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 7537a39..a5e9368 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -85,6 +85,7 @@
     bool isTrustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
+    scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
     ui::Transform::RotationFlags fixedTransformHint;
     std::optional<ui::Transform::RotationFlags> transformHint;
     bool handleSkipScreenshotFlag = false;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index da84e44..4c9fb06 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -813,11 +813,23 @@
                 RequestedLayerState::Changes::Hierarchy) ||
         snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
                              RequestedLayerState::Changes::Hierarchy)) {
-        snapshot.frameRate = requested.requestedFrameRate.isValid() ? requested.requestedFrameRate
-                                                                    : parentSnapshot.frameRate;
+        bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+                scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+        snapshot.frameRate = !requested.requestedFrameRate.isValid() || shouldOverrideChildren
+                ? parentSnapshot.frameRate
+                : requested.requestedFrameRate;
         snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                requested.frameRateSelectionStrategy);
+        snapshot.frameRateSelectionStrategy =
+                strategy == scheduler::LayerInfo::FrameRateSelectionStrategy::Self
+                ? parentSnapshot.frameRateSelectionStrategy
+                : strategy;
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
         snapshot.frameRateSelectionPriority =
                 (requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 2bf8bc9..fcc1e61 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -125,6 +125,8 @@
     defaultFrameRateCompatibility =
             static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
     frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+    frameRateSelectionStrategy =
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
     dataspace = ui::Dataspace::V0_SRGB;
     gameMode = gui::GameMode::Unsupported;
     requestedFrameRate = {};
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 2684000..31b5e28 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -192,6 +192,7 @@
     mDrawingState.dropInputMode = gui::DropInputMode::NONE;
     mDrawingState.dimmingEnabled = true;
     mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
+    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Self;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -1338,6 +1339,15 @@
     return true;
 }
 
+bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
+    if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
+    mDrawingState.frameRateSelectionStrategy = strategy;
+    mDrawingState.sequence++;
+    mDrawingState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
                                                       nsecs_t postTime) {
     mDrawingState.postTime = postTime;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0b0cef5..d5e2185 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -112,6 +112,7 @@
 
     using FrameRate = scheduler::LayerInfo::FrameRate;
     using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
+    using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
 
     struct State {
         int32_t z;
@@ -188,6 +189,8 @@
         // The combined frame rate of parents / children of this layer
         FrameRate frameRateForLayerTree;
 
+        FrameRateSelectionStrategy frameRateSelectionStrategy;
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
@@ -782,6 +785,8 @@
     bool setFrameRate(FrameRate::FrameRateVote);
     bool setFrameRateCategory(FrameRateCategory);
 
+    bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
+
     virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
     void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
     void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 0784251..875bdc8 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -116,12 +116,24 @@
         }
     }
 
+    // Vote the small dirty when a layer contains at least HISTORY_SIZE of small dirty updates.
+    bool isSmallDirty = false;
+    if (smallDirtyCount >= kNumSmallDirtyThreshold) {
+        if (mLastSmallDirtyCount >= HISTORY_SIZE) {
+            isSmallDirty = true;
+        } else {
+            mLastSmallDirtyCount++;
+        }
+    } else {
+        mLastSmallDirtyCount = 0;
+    }
+
     if (isFrequent || isInfrequent) {
         // If the layer was previously inconclusive, we clear
         // the history as indeterminate layers changed to frequent,
         // and we should not look at the stale data.
         return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true,
-                /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold};
+                isSmallDirty};
     }
 
     // If we can't determine whether the layer is frequent or not, we return
@@ -324,6 +336,7 @@
         ATRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
+        mLastSmallDirtyCount = 0;
         // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         votes.push_back({LayerHistory::LayerVoteType::Min, Fps()});
@@ -334,7 +347,7 @@
         clearHistory(now);
     }
 
-    // Return no vote if the latest frames are small dirty.
+    // Return no vote if the recent frames are small dirty.
     if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
         ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
         ALOGV("%s is small dirty", mName.c_str());
@@ -490,6 +503,19 @@
     }
 }
 
+LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
+        int8_t strategy) {
+    switch (strategy) {
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+            return FrameRateSelectionStrategy::Self;
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
+            return FrameRateSelectionStrategy::OverrideChildren;
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
+            return FrameRateSelectionStrategy::Self;
+    }
+}
+
 bool LayerInfo::FrameRate::isNoVote() const {
     return vote.type == FrameRateCompatibility::NoVote;
 }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 7fe407f..129b4c4 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -96,6 +96,13 @@
         ftl_last = NoVote
     };
 
+    enum class FrameRateSelectionStrategy {
+        Self,
+        OverrideChildren,
+
+        ftl_last = OverrideChildren
+    };
+
     // Encapsulates the frame rate specifications of the layer. This information will be used
     // when the display refresh rate is determined.
     struct FrameRate {
@@ -139,11 +146,11 @@
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
 
         // Convert an ANATIVEWINDOW_CHANGE_FRAME_RATE_* value to a scheduler::Seamlessness.
-        // Logs fatal if the compatibility value is invalid.
+        // Logs fatal if the strategy value is invalid.
         static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
 
         // Convert an ANATIVEWINDOW_FRAME_RATE_CATEGORY_* value to a FrameRateCategory.
-        // Logs fatal if the compatibility value is invalid.
+        // Logs fatal if the category value is invalid.
         static FrameRateCategory convertCategory(int8_t category);
 
         // True if the FrameRate has explicit frame rate specifications.
@@ -164,6 +171,10 @@
         }
     };
 
+    // Convert an ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_* value to FrameRateSelectionStrategy.
+    // Logs fatal if the strategy value is invalid.
+    static FrameRateSelectionStrategy convertFrameRateSelectionStrategy(int8_t strategy);
+
     static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
 
     LayerInfo(const std::string& name, uid_t ownerUid, LayerHistory::LayerVoteType defaultVote);
@@ -346,6 +357,10 @@
 
     RefreshRateHistory mRefreshRateHistory;
 
+    // This will be accessed from only one thread when counting a layer is frequent or infrequent,
+    // and to determine whether a layer is in small dirty updating.
+    mutable int32_t mLastSmallDirtyCount = 0;
+
     mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
 
     // Shared for all LayerInfo instances
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f1a45cb..fadde51 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -5206,6 +5206,14 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
+                scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                        s.frameRateSelectionStrategy);
+        if (layer->setFrameRateSelectionStrategy(strategy)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eFixedTransformHintChanged) {
         if (layer->setFixedTransformHint(s.fixedTransformHint)) {
             flags |= eTraversalNeeded | eTransformHintUpdateNeeded;
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index acfde71..d7ac038 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -346,6 +346,18 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eFrameRateSelectionStrategyChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setRoundedCorners(uint32_t id, float radius) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index c432ad0..7e3e61f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1124,8 +1124,8 @@
 
     LayerHistory::Summary summary;
 
-    // layer1 is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    // layer1 is updating small dirty.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
         auto props = layer1->getLayerProps();
         props.isSmallDirty = true;
         history().record(layer1->getSequence(), props, 0 /*presentTime*/, time,
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 767dd4f..1a9233d 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -743,6 +743,50 @@
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
 }
 
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set to 244.f with strategy OverrideChildren)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(12, 244.f, 0, 0);
+    setFrameRate(122, 123.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::LayerInfo::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
 TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
     setRoundedCorners(1, 42.f);
     setRoundedCorners(2, 42.f);
diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
index 8e28a75..11723c7 100644
--- a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
+++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
@@ -21,7 +22,8 @@
 #include <future>
 
 namespace android {
-using Transaction = SurfaceComposerClient::Transaction;
+
+using base::HwTimeoutMultiplier;
 using gui::DisplayInfo;
 using gui::WindowInfo;
 
@@ -36,7 +38,8 @@
         auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
         mClient->addWindowInfosListener(listener);
         auto future = promise.get_future();
-        bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready;
+        bool satisfied = future.wait_for(std::chrono::seconds{5 * HwTimeoutMultiplier()}) ==
+                std::future_status::ready;
         mClient->removeWindowInfosListener(listener);
         return satisfied;
     }