diff options
45 files changed, 2005 insertions, 164 deletions
diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml index 39772aece2..a20993f924 100644 --- a/data/etc/input/motion_predictor_config.xml +++ b/data/etc/input/motion_predictor_config.xml @@ -31,5 +31,13 @@ the UX issue mentioned above. --> <distance-noise-floor>0.2</distance-noise-floor> + <!-- The low and high jerk thresholds for prediction pruning. + + The jerk thresholds are based on normalized dt = 1 calculations, and + are taken from Jetpacks MotionEventPredictor's KalmanPredictor + implementation (using its ACCURATE_LOW_JANK and ACCURATE_HIGH_JANK). + --> + <low-jerk>0.1</low-jerk> + <high-jerk>0.2</high-jerk> </motion-predictor> diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h index 560e804c68..611478cbeb 100644 --- a/include/input/InputConsumer.h +++ b/include/input/InputConsumer.h @@ -111,6 +111,11 @@ private: std::shared_ptr<InputChannel> mChannel; + // TODO(b/311142655): delete this temporary tracing after the ANR bug is fixed + const std::string mProcessingTraceTag; + const std::string mLifetimeTraceTag; + const int32_t mLifetimeTraceCookie; + // The current input message. InputMessage mMsg; diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index 2edc138f67..728a8e1e39 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -105,6 +105,11 @@ public: // The noise floor for predictions. // Distances (r) less than this should be discarded as noise. float distanceNoiseFloor = 0; + + // Low and high jerk thresholds (with normalized dt = 1) for predictions. + // High jerk means more predictions will be pruned, vice versa for low. + float lowJerk = 0; + float highJerk = 0; }; // Creates a model from an encoded Flatbuffer model. diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp index b5c4010c7a..e888b0aea8 100644 --- a/libs/binderthreadstate/test.cpp +++ b/libs/binderthreadstate/test.cpp @@ -22,6 +22,7 @@ #include <binderthreadstateutilstest/1.0/IHidlStuff.h> #include <gtest/gtest.h> #include <hidl/HidlTransportSupport.h> +#include <hidl/ServiceManagement.h> #include <hwbinder/IPCThreadState.h> #include <thread> @@ -37,6 +38,7 @@ using android::OK; using android::sp; using android::String16; using android::binder::Status; +using android::hardware::isHidlSupported; using android::hardware::Return; using binderthreadstateutilstest::V1_0::IHidlStuff; @@ -67,6 +69,7 @@ std::string id2name(size_t id) { // complicated calls are possible, but this should do here. static void callHidl(size_t id, int32_t idx) { + CHECK_EQ(true, isHidlSupported()) << "We shouldn't be calling HIDL if it's not supported"; auto stuff = IHidlStuff::getService(id2name(id)); CHECK(stuff->call(idx).isOk()); } @@ -174,6 +177,7 @@ TEST(BinderThreadState, DoesntInitializeBinderDriver) { } TEST(BindThreadState, RemoteHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(0).isOk()); @@ -186,11 +190,14 @@ TEST(BindThreadState, RemoteAidlCall) { } TEST(BindThreadState, RemoteNestedStartHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(100).isOk()); } TEST(BindThreadState, RemoteNestedStartAidlCall) { + // this test case is trying ot nest a HIDL call which requires HIDL support + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; sp<IAidlStuff> stuff; ASSERT_EQ(OK, android::getService<IAidlStuff>(String16(id2name(kP1Id).c_str()), &stuff)); ASSERT_NE(nullptr, stuff); @@ -205,11 +212,15 @@ int server(size_t thisId, size_t otherId) { defaultServiceManager()->addService(String16(id2name(thisId).c_str()), aidlServer)); android::ProcessState::self()->startThreadPool(); - // HIDL - android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); - sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId); - CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); - android::hardware::joinRpcThreadpool(); + if (isHidlSupported()) { + // HIDL + android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); + sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId); + CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); + android::hardware::joinRpcThreadpool(); + } else { + android::IPCThreadState::self()->joinThreadPool(true); + } return EXIT_FAILURE; } @@ -227,9 +238,15 @@ int main(int argc, char** argv) { } android::waitForService<IAidlStuff>(String16(id2name(kP1Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP1Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP1Id).c_str()); + } android::waitForService<IAidlStuff>(String16(id2name(kP2Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP2Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP2Id).c_str()); + } return RUN_ALL_TESTS(); } diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 2d1b51a418..e4f1890c76 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -178,6 +178,8 @@ struct WindowInfo : public Parcelable { static_cast<uint32_t>(os::InputConfig::CLONE), GLOBAL_STYLUS_BLOCKS_TOUCH = static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH), + SENSITIVE_FOR_TRACING = + static_cast<uint32_t>(os::InputConfig::SENSITIVE_FOR_TRACING), // clang-format on }; diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 7222c607e7..f441eaa95a 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -197,7 +197,7 @@ public: EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); } - void expectTap(int x, int y) { + void expectTap(float x, float y) { InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); @@ -270,6 +270,11 @@ public: EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS); } + void assertNoEvent() { + InputEvent* event = consumeEvent(/*timeout=*/100ms); + ASSERT_EQ(event, nullptr) << "Expected no event, but got " << *event; + } + virtual ~InputSurface() { if (mClientChannel) { mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken()); @@ -939,9 +944,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(1, 1); surface->requestFocus(); surface->assertFocusChange(true); @@ -958,9 +961,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(.5, .5); surface->requestFocus(); surface->assertFocusChange(true); @@ -979,12 +980,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) { obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222}; obscuringSurface->showAt(100, 100); injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { @@ -1000,12 +1001,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { @@ -1022,12 +1023,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { @@ -1044,12 +1045,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { injectTap(111, 111); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) { @@ -1073,13 +1074,12 @@ TEST_F(InputSurfacesTest, drop_input_policy) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) { @@ -1114,7 +1114,7 @@ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_ // Does not receive events outside its crop injectTap(26, 26); - EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } /** @@ -1139,7 +1139,7 @@ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_nul // Does not receive events outside parent bounds injectTap(31, 31); - EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } /** @@ -1165,7 +1165,7 @@ TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) { // Does not receive events outside crop layer bounds injectTap(21, 21); injectTap(71, 71); - EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { @@ -1182,7 +1182,7 @@ TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); }); injectTap(101, 101); - EXPECT_EQ(parent->consumeEvent(/*timeout=*/100ms), nullptr); + parent->assertNoEvent(); } class MultiDisplayTests : public InputSurfacesTest { @@ -1231,7 +1231,7 @@ TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) { // Touches should be dropped if the layer is on an invalid display. injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); // However, we still let the window be focused and receive keys. surface->requestFocus(layerStack.id); @@ -1269,12 +1269,12 @@ TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(layerStack.id); surface->assertFocusChange(true); injectKeyOnDisplay(AKEYCODE_V, layerStack.id); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { @@ -1294,8 +1294,7 @@ TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { surface->showAt(100, 100); injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(1, 1); surface->requestFocus(layerStack.id); surface->assertFocusChange(true); diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp index 41ecfe3cfd..be2110e42b 100644 --- a/libs/input/InputConsumer.cpp +++ b/libs/input/InputConsumer.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <cstdint> #define LOG_TAG "InputTransport" #define ATRACE_TAG ATRACE_TAG_INPUT @@ -194,9 +195,21 @@ InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel) InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel, bool enableTouchResampling) - : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {} + : mResampleTouch(enableTouchResampling), + mChannel(channel), + mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)", + mChannel->getName().c_str(), this)), + mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)", + mChannel->getName().c_str(), this)), + mLifetimeTraceCookie( + static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)), + mMsgDeferred(false) { + ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie); +} -InputConsumer::~InputConsumer() {} +InputConsumer::~InputConsumer() { + ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie); +} bool InputConsumer::isTouchResamplingEnabled() { return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true); @@ -228,7 +241,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum mMsg.header.seq); // Trace the event processing timeline - event was just read from the socket - ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq); + ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq); } if (result) { // Consume the next batched event unless batches are being held for later. @@ -769,7 +782,7 @@ status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) popConsumeTime(seq); // Trace the event processing timeline - event was just finished - ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq); + ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq); } return result; } diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index 77292d4798..5b61d3953f 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -18,6 +18,7 @@ #include <input/MotionPredictor.h> +#include <algorithm> #include <array> #include <cinttypes> #include <cmath> @@ -62,6 +63,11 @@ TfLiteMotionPredictorSample::Point convertPrediction( return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta}; } +float normalizeRange(float x, float min, float max) { + const float normalized = (x - min) / (max - min); + return std::min(1.0f, std::max(0.0f, normalized)); +} + } // namespace // --- JerkTracker --- @@ -255,6 +261,17 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { int64_t predictionTime = mBuffers->lastTimestamp(); const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos; + const float jerkMagnitude = mJerkTracker.jerkMagnitude().value_or(0); + const float fractionKept = + 1 - normalizeRange(jerkMagnitude, mModel->config().lowJerk, mModel->config().highJerk); + // float to ensure proper division below. + const float predictionTimeWindow = futureTime - predictionTime; + const int maxNumPredictions = static_cast<int>( + std::ceil(predictionTimeWindow / mModel->config().predictionInterval * fractionKept)); + ALOGD_IF(isDebug(), + "jerk (d^3p/normalizedDt^3): %f, fraction of prediction window pruned: %f, max number " + "of predictions: %d", + jerkMagnitude, 1 - fractionKept, maxNumPredictions); for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime; ++i) { if (predictedR[i] < mModel->config().distanceNoiseFloor) { @@ -269,13 +286,12 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { break; } if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) { - // TODO(b/266747654): Stop predictions if confidence is < some threshold - // Arbitrarily high pruning index, will correct once jerk thresholding is implemented. - const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max(); - if (i > upperBoundPredictionIndex) { + if (i >= static_cast<size_t>(maxNumPredictions)) { break; } } + // TODO(b/266747654): Stop predictions if confidence is < some + // threshold. Currently predictions are pruned via jerk thresholding. const TfLiteMotionPredictorSample::Point predictedPoint = convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]); diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index d17476e216..b843a4bbf6 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -281,6 +281,8 @@ std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create() Config config{ .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"), .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"), + .lowJerk = parseXMLFloat(*configRoot, "low-jerk"), + .highJerk = parseXMLFloat(*configRoot, "high-jerk"), }; return std::unique_ptr<TfLiteMotionPredictorModel>( diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl index 5d391551c2..6b97cbbc59 100644 --- a/libs/input/android/os/InputConfig.aidl +++ b/libs/input/android/os/InputConfig.aidl @@ -157,4 +157,12 @@ enum InputConfig { * like StatusBar and TaskBar. */ GLOBAL_STYLUS_BLOCKS_TOUCH = 1 << 17, + + /** + * InputConfig used to indicate that this window is sensitive for tracing. + * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE}, + * but it may also be set without setting FLAG_SECURE. The tracing configuration will + * determine how these sensitive events are eventually traced. + */ + SENSITIVE_FOR_TRACING = 1 << 18, } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index e041c51cc9..e161c2afc1 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -137,3 +137,10 @@ flag { is_fixed_read_only: true } + +flag { + name: "enable_multi_device_same_window_stream" + namespace: "input" + description: "Allow multiple input devices to be active in the same window simultaneously" + bug: "330752824" +} diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index e67a65a114..ee140b72bd 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -36,6 +36,7 @@ cc_test { "tensorflow_headers", ], static_libs: [ + "libflagtest", "libgmock", "libgui_window_info_static", "libinput", diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index f74874cfe9..dc38feffd6 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -14,9 +14,12 @@ * limitations under the License. */ +// TODO(b/331815574): Decouple this test from assumed config values. #include <chrono> #include <cmath> +#include <com_android_input_flags.h> +#include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <gui/constants.h> @@ -197,18 +200,14 @@ TEST(MotionPredictorTest, Offset) { TEST(MotionPredictorTest, FollowsGesture) { MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { return true /*enable prediction*/; }); - - // MOVE without a DOWN is ignored. - predictor.record(getMotionEvent(MOVE, 1, 3, 10ms)); - EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); - - predictor.record(getMotionEvent(DOWN, 2, 5, 20ms)); - predictor.record(getMotionEvent(MOVE, 2, 7, 30ms)); - predictor.record(getMotionEvent(MOVE, 3, 9, 40ms)); - EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC)); - - predictor.record(getMotionEvent(UP, 4, 11, 50ms)); - EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); + predictor.record(getMotionEvent(DOWN, 3.75, 3, 20ms)); + predictor.record(getMotionEvent(MOVE, 4.8, 3, 30ms)); + predictor.record(getMotionEvent(MOVE, 6.2, 3, 40ms)); + predictor.record(getMotionEvent(MOVE, 8, 3, 50ms)); + EXPECT_NE(nullptr, predictor.predict(90 * NSEC_PER_MSEC)); + + predictor.record(getMotionEvent(UP, 10.25, 3, 60ms)); + EXPECT_EQ(nullptr, predictor.predict(100 * NSEC_PER_MSEC)); } TEST(MotionPredictorTest, MultipleDevicesNotSupported) { @@ -250,6 +249,63 @@ TEST(MotionPredictorTest, FlagDisablesPrediction) { ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); } +TEST_WITH_FLAGS( + MotionPredictorTest, LowJerkNoPruning, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is low (0.05 normalized). + predictor.record(getMotionEvent(DOWN, 2, 7, 20ms)); + predictor.record(getMotionEvent(MOVE, 2.75, 7, 30ms)); + predictor.record(getMotionEvent(MOVE, 3.8, 7, 40ms)); + predictor.record(getMotionEvent(MOVE, 5.2, 7, 50ms)); + predictor.record(getMotionEvent(MOVE, 7, 7, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC); + EXPECT_NE(nullptr, predicted); + EXPECT_EQ(static_cast<size_t>(5), predicted->getHistorySize() + 1); +} + +TEST_WITH_FLAGS( + MotionPredictorTest, HighJerkPredictionsPruned, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is incredibly high. + predictor.record(getMotionEvent(DOWN, 0, 5, 20ms)); + predictor.record(getMotionEvent(MOVE, 0, 70, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 139, 40ms)); + predictor.record(getMotionEvent(MOVE, 0, 1421, 50ms)); + predictor.record(getMotionEvent(MOVE, 0, 41233, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC); + EXPECT_EQ(nullptr, predicted); +} + +TEST_WITH_FLAGS( + MotionPredictorTest, MediumJerkPredictionsSomePruned, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is medium (1.5 normalized, which is halfway between LOW_JANK and HIGH_JANK) + predictor.record(getMotionEvent(DOWN, 0, 4, 20ms)); + predictor.record(getMotionEvent(MOVE, 0, 6.25, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 9.4, 40ms)); + predictor.record(getMotionEvent(MOVE, 0, 13.6, 50ms)); + predictor.record(getMotionEvent(MOVE, 0, 19, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(82 * NSEC_PER_MSEC); + EXPECT_NE(nullptr, predicted); + // Halfway between LOW_JANK and HIGH_JANK means that half of the predictions + // will be pruned. If model prediction window is close enough to predict() + // call time window, then half of the model predictions (5/2 -> 2) will be + // ouputted. + EXPECT_EQ(static_cast<size_t>(3), predicted->getHistorySize() + 1); +} + using AtomFields = MotionPredictorMetricsManager::AtomFields; using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4d70a33632..fa62fa41d5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -838,6 +838,13 @@ Result<void> validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) { if (!inserted) { return Error() << "Duplicate entry for " << info; } + if (info.layoutParamsFlags.test(WindowInfo::Flag::SECURE) && + !info.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE) && + !info.inputConfig.test(WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)) { + return Error() + << "Window with FLAG_SECURE does not set InputConfig::SENSITIVE_FOR_TRACING: " + << info; + } } return {}; } @@ -2532,11 +2539,19 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (!isHoverAction) { const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; - tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS, - targetFlags, entry.deviceId, {pointer}, - isDownOrPointerDown - ? std::make_optional(entry.eventTime) - : std::nullopt); + Result<void> addResult = + tempTouchState.addOrUpdateWindow(windowHandle, + InputTarget::DispatchMode::AS_IS, + targetFlags, entry.deviceId, {pointer}, + isDownOrPointerDown + ? std::make_optional( + entry.eventTime) + : std::nullopt); + if (!addResult.ok()) { + LOG(ERROR) << "Error while processing " << entry << " for " + << windowHandle->getName(); + logDispatchStateLocked(); + } // If this is the pointer going down and the touched window has a wallpaper // then also add the touched wallpaper windows so they are locked in for the // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or @@ -4383,7 +4398,7 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( // different pointer ids than we expected based on the previous ACTION_DOWN // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers // in this way. - ALOGW("Dropping split motion event because the pointer count is %d but " + ALOGW("Dropping split motion event because the pointer count is %zu but " "we expected there to be %zu pointers. This probably means we received " "a broken sequence of pointer ids from the input device: %s", pointerCoords.size(), pointerIds.count(), diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 1fec9b7599..c2d2b7b0ea 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -95,12 +95,14 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) { return true; } - if (!mMotionMementos.empty()) { - const MotionMemento& lastMemento = mMotionMementos.back(); - if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && - !isStylusEvent(entry.source, entry.pointerProperties)) { - // We already have a stylus stream, and the new event is not from stylus. - return false; + if (!input_flags::enable_multi_device_same_window_stream()) { + if (!mMotionMementos.empty()) { + const MotionMemento& lastMemento = mMotionMementos.back(); + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && + !isStylusEvent(entry.source, entry.pointerProperties)) { + // We already have a stylus stream, and the new event is not from stylus. + return false; + } } } @@ -345,24 +347,26 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) cons return false; } - if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) { - // A stylus is already active. - if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && - actionMasked == AMOTION_EVENT_ACTION_DOWN) { - // If this new event is from a different device, then cancel the old - // stylus and allow the new stylus to take over, but only if it's going down. - // Otherwise, they will start to race each other. - return true; - } + if (!input_flags::enable_multi_device_same_window_stream()) { + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) { + // A stylus is already active. + if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && + actionMasked == AMOTION_EVENT_ACTION_DOWN) { + // If this new event is from a different device, then cancel the old + // stylus and allow the new stylus to take over, but only if it's going down. + // Otherwise, they will start to race each other. + return true; + } - // Keep the current stylus gesture. - return false; - } + // Keep the current stylus gesture. + return false; + } - // Cancel the current gesture if this is a start of a new gesture from a new device. - if (actionMasked == AMOTION_EVENT_ACTION_DOWN || - actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { - return true; + // Cancel the current gesture if this is a start of a new gesture from a new device. + if (actionMasked == AMOTION_EVENT_ACTION_DOWN || + actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { + return true; + } } // By default, don't cancel any events. return false; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index f8aa62500e..0caa5e1402 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -70,14 +70,14 @@ void TouchState::clearWindowsWithoutPointers() { }); } -void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, - InputTarget::DispatchMode dispatchMode, - ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - const std::vector<PointerProperties>& touchingPointers, - std::optional<nsecs_t> firstDownTimeInTarget) { +android::base::Result<void> TouchState::addOrUpdateWindow( + const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode, + ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, + const std::vector<PointerProperties>& touchingPointers, + std::optional<nsecs_t> firstDownTimeInTarget) { if (touchingPointers.empty()) { LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName(); - return; + return android::base::Error(); } for (TouchedWindow& touchedWindow : windows) { // We do not compare windows by token here because two windows that share the same token @@ -91,11 +91,12 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have // downTime set initially. Need to update existing window when a pointer is down for the // window. - touchedWindow.addTouchingPointers(deviceId, touchingPointers); + android::base::Result<void> addResult = + touchedWindow.addTouchingPointers(deviceId, touchingPointers); if (firstDownTimeInTarget) { touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } - return; + return addResult; } } TouchedWindow touchedWindow; @@ -107,6 +108,7 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } windows.push_back(touchedWindow); + return {}; } void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 3d534bc71d..559a3fd041 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -43,11 +43,11 @@ struct TouchState { void removeTouchingPointer(DeviceId deviceId, int32_t pointerId); void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle); - void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, - InputTarget::DispatchMode dispatchMode, - ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - const std::vector<PointerProperties>& touchingPointers, - std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); + android::base::Result<void> addOrUpdateWindow( + const sp<android::gui::WindowInfoHandle>& windowHandle, + InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags, + DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers, + std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, DeviceId deviceId, const PointerProperties& pointer); void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 037d7c8e99..1f86f6635a 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -20,6 +20,7 @@ #include <android-base/stringprintf.h> #include <input/PrintTools.h> +using android::base::Result; using android::base::StringPrintf; namespace android { @@ -89,8 +90,8 @@ void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerPropertie hoveringPointers.push_back(pointer); } -void TouchedWindow::addTouchingPointers(DeviceId deviceId, - const std::vector<PointerProperties>& pointers) { +Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId, + const std::vector<PointerProperties>& pointers) { std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers; const size_t initialSize = touchingPointers.size(); for (const PointerProperties& pointer : pointers) { @@ -98,11 +99,14 @@ void TouchedWindow::addTouchingPointers(DeviceId deviceId, return properties.id == pointer.id; }); } - if (touchingPointers.size() != initialSize) { + const bool foundInconsistentState = touchingPointers.size() != initialSize; + touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end()); + if (foundInconsistentState) { LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device " << deviceId << " already in " << *this; + return android::base::Error(); } - touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end()); + return {}; } bool TouchedWindow::hasTouchingPointers() const { diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 0d1531f8ff..4f0ad1628a 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -46,7 +46,8 @@ struct TouchedWindow { bool hasTouchingPointers() const; bool hasTouchingPointers(DeviceId deviceId) const; std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const; - void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers); + android::base::Result<void> addTouchingPointers(DeviceId deviceId, + const std::vector<PointerProperties>& pointers); void removeTouchingPointer(DeviceId deviceId, int32_t pointerId); void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers); bool hasActiveStylus() const; diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 1d4d11c13a..f8ee95fa9a 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -86,8 +86,9 @@ InputTargetInfo getTargetInfo(const InputTarget& target) { // This is a global monitor, assume its target is the system. return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false}; } - return {target.windowHandle->getInfo()->ownerUid, - target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)}; + const bool isSensitiveTarget = target.windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING); + return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget}; } } // namespace diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp index 0ac2f0f7e6..bfe09bc4af 100644 --- a/services/inputflinger/tests/FakeWindows.cpp +++ b/services/inputflinger/tests/FakeWindows.cpp @@ -298,16 +298,18 @@ std::unique_ptr<MotionEvent> FakeWindowHandle::consumeMotionEvent( const ::testing::Matcher<MotionEvent>& matcher) { std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); if (event == nullptr) { - ADD_FAILURE() << "No event"; + std::ostringstream matcherDescription; + matcher.DescribeTo(&matcherDescription); + ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName; return nullptr; } if (event->getType() != InputEventType::MOTION) { - ADD_FAILURE() << "Instead of motion event, got " << *event; + ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName; return nullptr; } std::unique_ptr<MotionEvent> motionEvent = std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); - EXPECT_THAT(*motionEvent, matcher); + EXPECT_THAT(*motionEvent, matcher) << " on " << mName; return motionEvent; } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 4bb64fc9e1..05db1ef964 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -59,6 +59,8 @@ namespace android::inputdispatcher { using namespace ftl::flag_operators; using testing::AllOf; using testing::Not; +using testing::Pointee; +using testing::UnorderedElementsAre; namespace { @@ -132,6 +134,40 @@ static KeyEvent getTestKeyEvent() { return event; } +/** + * Provide a local override for a flag value. The value is restored when the object of this class + * goes out of scope. + * This class is not intended to be used directly, because its usage is cumbersome. + * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided. + */ +class ScopedFlagOverride { +public: + ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value) + : mInitialValue(read()), mWriteValue(write) { + mWriteValue(value); + } + ~ScopedFlagOverride() { mWriteValue(mInitialValue); } + +private: + const bool mInitialValue; + std::function<void(bool)> mWriteValue; +}; + +typedef bool (*readFlagValueFunction)(); +typedef void (*writeFlagValueFunction)(bool); + +/** + * Use this macro to locally override a flag value. + * Example usage: + * SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); + * Note: this works by creating a local variable in your current scope. Don't call this twice for + * the same flag, because the variable names will clash! + */ +#define SCOPED_FLAG_OVERRIDE(NAME, VALUE) \ + readFlagValueFunction read##NAME = com::android::input::flags::NAME; \ + writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \ + ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE)) + } // namespace // --- InputDispatcherTest --- @@ -1257,7 +1293,9 @@ TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) { * This test reproduces a crash where there is a mismatch between the downTime and eventTime. * In the buggy implementation, a tap on the right window would cause a crash. */ -TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -1353,6 +1391,99 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { } /** + * Two windows: a window on the left and a window on the right. + * Mouse is hovered from the right window into the left window. + * Next, we tap on the left window, where the cursor was last seen. + * The second tap is done onto the right window. + * The mouse and tap are from two different devices. + * We technically don't need to set the downtime / eventtime for these events, but setting these + * explicitly helps during debugging. + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, a tap on the right window would cause a crash. + */ +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + // All times need to start at the current time, otherwise the dispatcher will drop the events as + // stale. + const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC); + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Move the cursor from right + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 20) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // .. to the left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 30) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // Now tap the left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 40) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 50) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Tap the window on the right + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 60) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 70) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Start hovering in a window. While this hover is still active, make another window appear on top. * The top, obstructing window has no input channel, so it's not supposed to receive input. * While the top window is present, the hovering is stopped. @@ -1500,6 +1631,7 @@ using InputDispatcherMultiDeviceTest = InputDispatcherTest; * touch is dropped, because stylus should be preferred over touch. */ TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1542,11 +1674,60 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { } /** + * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that + * touch is not dropped, because multiple devices are allowed to be active in the same window. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Touch move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + window->assertNoEvents(); +} + +/** * One window and one spy window. Stylus down on the window. Next, touch from another device goes * down. Ensure that touch is dropped, because stylus should be preferred over touch. * Similar test as above, but with added SPY window. */ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1600,10 +1781,74 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { } /** + * One window and one spy window. Stylus down on the window. Next, touch from another device goes + * down. Ensure that touch is not dropped, because multiple devices can be active at the same time. + * Similar test as above, but with added SPY window. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Touch move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + +/** * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that * touch is dropped, because stylus hover takes precedence. */ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1651,10 +1896,65 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { } /** + * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that + * touch is not dropped, because stylus hover and touch can be both active at the same time. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + // Touch move on window + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), + WithDeviceId(stylusDeviceId), WithCoords(101, 111))); + + // and subsequent touches continue to work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + window->assertNoEvents(); +} + +/** * One window. Touch down on the window. Then, stylus hover on the window from another device. * Ensure that touch is canceled, because stylus hover should take precedence. */ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1704,11 +2004,66 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { } /** + * One window. Touch down on the window. Then, stylus hover on the window from another device. + * Ensure that touch is not canceled, because stylus hover can be active at the same time as touch. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus hover on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + // Stylus hover movement is received normally + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), + WithDeviceId(stylusDeviceId), WithCoords(100, 110))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), + WithDeviceId(stylusDeviceId), WithCoords(101, 111))); + + // Subsequent touch movements also work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), + WithCoords(142, 147))); + + window->assertNoEvents(); +} + +/** * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should * become active. */ TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1756,10 +2111,59 @@ TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { } /** + * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that + * both stylus devices can function simultaneously. + */ +TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t stylusDeviceId1 = 3; + constexpr int32_t stylusDeviceId2 = 5; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1))); + + // Second stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1))); + window->assertNoEvents(); +} + +/** * One window. Touch down on the window. Then, stylus down on the window from another device. * Ensure that is canceled, because stylus down should be preferred over touch. */ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -1800,13 +2204,65 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { } /** + * One window. Touch down on the window. Then, stylus down on the window from another device. + * Ensure that both touch and stylus are functioning independently. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + // Touch continues to work too + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(148).y(149)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); +} + +/** * Two windows: a window on the left and a window on the right. * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains * down. Then, on the left window, also place second touch pointer down. * This test tries to reproduce a crash. * In the buggy implementation, second pointer down on the left window would cause a crash. */ -TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -1885,6 +2341,88 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { /** * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // Start hovering over the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); + // Since this is now a new splittable pointer going down on the left window, and it's coming + // from a different device, it will be split and delivered to left window separately. + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * Two windows: a window on the left and a window on the right. * Mouse is hovered on the left window and stylus is hovered on the right window. */ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { @@ -1944,7 +2482,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { * Stylus down on the left window and remains down. Touch goes down on the right and remains down. * Check the stream that's received by the spy. */ -TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -2014,6 +2553,83 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { } /** + * Three windows: a window on the left and a window on the right. + * And a spy window that's positioned above all of them. + * Stylus down on the left window and remains down. Touch goes down on the right and remains down. + * Check the stream that's received by the spy. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 400, 400)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 1; + const int32_t touchDeviceId = 2; + + // Stylus down on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down on the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Stylus movements continue. They should be delivered to the left window and to the spy window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + + // Further touch MOVE events keep going to the right window and to the spy + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + spyWindow->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Three windows: a window on the left, a window on the right, and a spy window positioned above * both. * Check hover in left window and touch down in the right window. @@ -2022,6 +2638,7 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { * respectively. */ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -2089,6 +2706,84 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { } /** + * Three windows: a window on the left, a window on the right, and a spy window positioned above + * both. + * Check hover in left window and touch down in the right window. + * At first, spy should receive hover. Next, spy should receive touch. + * At the same time, left and right should be getting independent streams of hovering and touch, + * respectively. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 400, 400)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 1; + const int32_t touchDeviceId = 2; + + // Stylus hover on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + + // Touch down on the right window. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Stylus movements continue. They should be delivered to the left window and the spy. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + + // Touch movements continue. They should be delivered to the right window and the spy + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + spyWindow->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * On a single window, use two different devices: mouse and touch. * Touch happens first, with two pointers going down, and then the first pointer leaving. * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. @@ -2096,7 +2791,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not * represent a new gesture. */ -TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { +TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2169,10 +2865,89 @@ TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { } /** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which should not interfere with the touch stream. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is also + * delivered correctly. + */ +TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 400, 400)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // First touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + // Second touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_0_DOWN), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + + // Mouse movements should continue to work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + + window->assertNoEvents(); +} + +/** * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels * the injected event. */ -TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { +TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2205,6 +2980,40 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { } /** + * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event runs + * parallel to the injected event. + */ +TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 400, 400)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after + // completion. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, + MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + + // Now a real touch comes. The injected pointer will remain, and the new gesture will also be + // allowed through. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * This test is similar to the test above, but the sequence of injected events is different. * * Two windows: a window on the left and a window on the right. @@ -2218,7 +3027,8 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { * This test reproduces a crash where there is a mismatch between the downTime and eventTime. * In the buggy implementation, second finger down on the left window would cause a crash. */ -TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { +TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -2290,11 +3100,88 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { } /** + * This test is similar to the test above, but the sequence of injected events is different. + * + * Two windows: a window on the left and a window on the right. + * Mouse is hovered over the left window. + * Next, we tap on the left window, where the cursor was last seen. + * + * After that, we send one finger down onto the right window, and then a second finger down onto + * the left window. + * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right + * window (first), and then another on the left window (second). + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, second finger down on the left window would cause a crash. + */ +TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Hover over the left window. Keep the cursor there. + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Tap on left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDeviceId(touchDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDeviceId(touchDeviceId))); + + // First finger down on right window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Second finger down on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. * While the touch is down, new hover events from the stylus device should be ignored. After the * touch is gone, stylus hovering should start working again. */ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2356,6 +3243,61 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { } /** + * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. + * While the touch is down, hovering from the stylus is not affected. After the touch is gone, + * check that the stylus hovering continues to work. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + // Start hovering with stylus + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Finger down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Continue hovering with stylus. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60)) + .build()); + // Hovers continue to work + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + + // Lift up the finger + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + window->assertNoEvents(); +} + +/** * If stylus is down anywhere on the screen, then touches should not be delivered to windows that * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH. * @@ -2601,7 +3543,8 @@ TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) { * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. * While the mouse is down, new move events from the touch device should be ignored. */ -TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); @@ -2698,6 +3641,114 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { } /** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should continue to work. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + // Hover is not pilfered! Only touch. + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + + // Touch move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. * Make sure that the window receives the second pointer, and first pointer is simply ignored. @@ -2910,7 +3961,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the * currently active gesture should be canceled, and the new one should proceed. */ -TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2964,6 +4016,65 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { window->assertNoEvents(); } +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should not be canceled, and the new one should proceed in parallel. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 600, 800)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // Two pointers down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Send a series of mouse events for a mouse click + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already active gesture, it should be sent normally. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + window->assertNoEvents(); +} + TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -2996,7 +4107,8 @@ TEST_F(InputDispatcherTest, HoverWithSpyWindows) { spyWindow->assertNoEvents(); } -TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -3102,6 +4214,102 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { spyWindow->assertNoEvents(); } +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Send mouse cursor to the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + // Move mouse cursor + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + // Touch down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // pilfer the motion, retaining the gesture on the spy window. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + // Mouse hover is not pilfered + + // Touch UP on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going + // to send a new gesture. It should again go to both windows (spy and the window below), just + // like the first gesture did, before pilfering. The window configuration has not changed. + + // One more tap - DOWN + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Mouse movement continues normally as well + // Move mouse cursor + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(120).y(130)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected // directly in this test. TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { @@ -3227,7 +4435,8 @@ TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash, /** * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. */ -TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3258,11 +4467,43 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { } /** + * If mouse is hovering when the touch goes down, the hovering should not be stopped. + */ +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 100, 100)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Start hovering with the mouse + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Touch goes down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * Inject a mouse hover event followed by a tap from touchscreen. * The tap causes a HOVER_EXIT event to be generated because the current event * stream's source has been switched. */ -TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { +TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3296,6 +4537,45 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); } +/** + * Send a mouse hover event followed by a tap from touchscreen. + * The tap causes a HOVER_EXIT event to be generated because the current event + * stream's source has been switched. + */ +TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + 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, 100, 100)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + + // Tap on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); +} + TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> windowDefaultDisplay = @@ -10901,7 +12181,8 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { * Pilfer from spy window. * Check that the pilfering only affects the pointers that are actually being received by the spy. */ -TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { +TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); sp<FakeWindowHandle> spy = createSpy(); spy->setFrame(Rect(0, 0, 200, 200)); sp<FakeWindowHandle> leftWindow = createForeground(); @@ -10959,6 +12240,80 @@ TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { rightWindow->assertNoEvents(); } +/** + * A window on the left and a window on the right. Also, a spy window that's above all of the + * windows, and spanning both left and right windows. + * Send simultaneous motion streams from two different devices, one to the left window, and another + * to the right window. + * Pilfer from spy window. + * Check that the pilfering affects all of the pointers that are actually being received by the spy. + * The spy should receive both the touch and the stylus events after pilfer. + */ +TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + sp<FakeWindowHandle> spy = createSpy(); + spy->setFrame(Rect(0, 0, 200, 200)); + sp<FakeWindowHandle> leftWindow = createForeground(); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + + sp<FakeWindowHandle> rightWindow = createForeground(); + rightWindow->setFrame(Rect(100, 0, 200, 100)); + + constexpr int32_t stylusDeviceId = 1; + constexpr int32_t touchDeviceId = 2; + + mDispatcher->onWindowInfosChanged( + {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + // Stylus down on left window and spy + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Finger down on right window and spy + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Act: pilfer from spy. Spy is currently receiving touch events. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + + // Continue movements from both stylus and touch. Touch and stylus will be delivered to spy + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52)) + .build()); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52)) + .build()); + std::vector<std::unique_ptr<MotionEvent>> spyEvents; + spyEvents.push_back(spy->consumeMotionEvent(WithMotionAction(ACTION_MOVE))); + spyEvents.push_back(spy->consumeMotionEvent(WithMotionAction(ACTION_MOVE))); + // TODO(b/332314982) : Figure out why these can be out of order + ASSERT_THAT(spyEvents, + UnorderedElementsAre(Pointee(WithDeviceId(stylusDeviceId)), + Pointee(WithDeviceId(touchDeviceId)))); + + spy->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) { auto window = createForeground(); auto spy = createSpy(); @@ -11423,7 +12778,8 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { /*pointerId=*/0)); } -TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { +TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window", @@ -11493,4 +12849,76 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { /*pointerId=*/0)); } +/** + * TODO(b/313689709) - correctly support multiple mouse devices, because they should be controlling + * the same cursor, and therefore have a shared motion event stream. + */ +TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window", + ADISPLAY_ID_DEFAULT); + left->setFrame(Rect(0, 0, 100, 100)); + sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher, + "Right Window", ADISPLAY_ID_DEFAULT); + right->setFrame(Rect(100, 0, 200, 100)); + + mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0}); + + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + + // Hover move into the window. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50)) + .rawXCursorPosition(50) + .rawYCursorPosition(50) + .deviceId(DEVICE_ID) + .build()); + + left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + + // Move the mouse with another device + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50)) + .rawXCursorPosition(51) + .rawYCursorPosition(50) + .deviceId(SECOND_DEVICE_ID) + .build()); + left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets + // a HOVER_EXIT from the first device. + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + SECOND_DEVICE_ID, + /*pointerId=*/0)); + + // Move the mouse outside the window. Document the current behavior, where the window does not + // receive HOVER_EXIT even though the mouse left the window. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50)) + .rawXCursorPosition(150) + .rawYCursorPosition(50) + .deviceId(SECOND_DEVICE_ID) + .build()); + + right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + SECOND_DEVICE_ID, + /*pointerId=*/0)); +} + } // namespace android::inputdispatcher diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index e2d17ee502..86bcf20677 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -143,7 +143,7 @@ public: compositionengine::OutputLayer* getBlurLayer() const; - bool hasUnsupportedDataspace() const; + bool hasKnownColorShift() const; bool hasProtectedLayers() const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index dc3821ca43..5e3e3d8a31 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -74,6 +74,7 @@ enum class LayerStateField : uint32_t { BlurRegions = 1u << 18, HasProtectedContent = 1u << 19, CachingHint = 1u << 20, + DimmingEnabled = 1u << 21, }; // clang-format on @@ -248,6 +249,10 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } + hardware::graphics::composer::hal::PixelFormat getPixelFormat() const { + return mPixelFormat.get(); + } + float getHdrSdrRatio() const { return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio; }; @@ -258,6 +263,8 @@ public: gui::CachingHint getCachingHint() const { return mCachingHint.get(); } + bool isDimmingEnabled() const { return mIsDimmingEnabled.get(); } + float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; } void dump(std::string& result) const; @@ -498,7 +505,10 @@ private: return std::vector<std::string>{toString(cachingHint)}; }}; - static const constexpr size_t kNumNonUniqueFields = 19; + OutputLayerState<bool, LayerStateField::DimmingEnabled> mIsDimmingEnabled{ + [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }}; + + static const constexpr size_t kNumNonUniqueFields = 20; std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() { std::array<const StateInterface*, kNumNonUniqueFields> constFields = @@ -516,7 +526,7 @@ private: &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, &mIsProtected, &mCachingHint}; + &mFrameNumber, &mIsProtected, &mCachingHint, &mIsDimmingEnabled}; } }; diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 1f53588412..ea9442da06 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -27,8 +27,7 @@ #include <renderengine/DisplaySettings.h> #include <renderengine/RenderEngine.h> #include <ui/DebugUtils.h> -#include <utils/Trace.h> - +#include <ui/HdrRenderTypeUtils.h> #include <utils/Trace.h> namespace android::compositionengine::impl::planner { @@ -306,7 +305,7 @@ bool CachedSet::requiresHolePunch() const { return false; } - if (hasUnsupportedDataspace()) { + if (hasKnownColorShift()) { return false; } @@ -366,12 +365,21 @@ compositionengine::OutputLayer* CachedSet::getBlurLayer() const { return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr; } -bool CachedSet::hasUnsupportedDataspace() const { +bool CachedSet::hasKnownColorShift() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { auto dataspace = layer.getState()->getDataspace(); - const auto transfer = static_cast<ui::Dataspace>(dataspace & ui::Dataspace::TRANSFER_MASK); - if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) { - // Skip HDR. + + // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to + // dim a cached set. But this means that we can never cache any HDR layers so that we + // don't accidentally dim those layers. + const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(), + layer.getState()->getHdrSdrRatio()); + if (hdrType != HdrRenderType::SDR) { + return true; + } + + // Layers that have dimming disabled pretend that they're HDR. + if (!layer.getState()->isDimmingEnabled()) { return true; } @@ -380,10 +388,6 @@ bool CachedSet::hasUnsupportedDataspace() const { // to avoid flickering/color differences. return true; } - // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f - if (layer.getState()->getHdrSdrRatio() > 1.01f) { - return true; - } return false; }); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 0a5c43a99b..4bafed2c8e 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -439,7 +439,7 @@ std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const { if (!layerDeniedFromCaching && layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) && - !currentSet->hasUnsupportedDataspace()) { + !currentSet->hasKnownColorShift()) { if (isPartOfRun) { builder.increment(); } else { diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index d2eff75fb6..54ee0efa11 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -1339,6 +1339,55 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { EXPECT_EQ(nullptr, overrideBuffer3); } +TEST_F(FlattenerTest, flattenLayers_skipsLayersDisablingDimming) { + auto& layerState1 = mTestLayers[0]->layerState; + const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; + + auto& layerState2 = mTestLayers[1]->layerState; + const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; + + // The third layer disables dimming, which means it should not be cached + auto& layerState3 = mTestLayers[2]->layerState; + const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; + mTestLayers[2]->layerFECompositionState.dimmingEnabled = false; + mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); + + const std::vector<const LayerState*> layers = { + layerState1.get(), + layerState2.get(), + layerState3.get(), + }; + + initializeFlattener(layers); + + mTime += 200ms; + initializeOverrideBuffer(layers); + EXPECT_EQ(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + + // This will render a CachedSet. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) + .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + // We've rendered a CachedSet, but we haven't merged it in. + EXPECT_EQ(nullptr, overrideBuffer1); + EXPECT_EQ(nullptr, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); + + // This time we merge the CachedSet in, so we have a new hash, and we should + // only have two sets. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); + initializeOverrideBuffer(layers); + EXPECT_NE(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + EXPECT_NE(nullptr, overrideBuffer1); + EXPECT_EQ(overrideBuffer1, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); +} + TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { auto& layerState1 = mTestLayers[0]->layerState; const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 044917ead9..39fce2b629 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -304,6 +304,16 @@ TEST_F(LayerStateTest, getCompositionType_forcedClient) { EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType()); } +TEST_F(LayerStateTest, getHdrSdrRatio) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.currentHdrSdrRatio = 2.f; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio()); +} + TEST_F(LayerStateTest, updateCompositionType) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; @@ -1033,6 +1043,47 @@ TEST_F(LayerStateTest, compareCachingHint) { EXPECT_TRUE(otherLayerState->compare(*mLayerState)); } +TEST_F(LayerStateTest, updateDimmingEnabled) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.dimmingEnabled = true; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + EXPECT_TRUE(mLayerState->isDimmingEnabled()); + + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.dimmingEnabled = false; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer); + EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates); + EXPECT_FALSE(mLayerState->isDimmingEnabled()); +} + +TEST_F(LayerStateTest, compareDimmingEnabled) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.dimmingEnabled = true; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.dimmingEnabled = false; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer); + + verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled); + + EXPECT_TRUE(mLayerState->compare(*otherLayerState)); + EXPECT_TRUE(otherLayerState->compare(*mLayerState)); +} + TEST_F(LayerStateTest, dumpDoesNotCrash) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 62f8fb16f0..45ab7ddc4d 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -560,10 +560,8 @@ auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> return DesiredModeAction::InitiateRenderRateSwitch; } - // Set the render frame rate to the active physical refresh rate to schedule the next - // frame as soon as possible. setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(), - activeMode.modePtr->getVsyncRate()); + activeMode.modePtr->getPeakFps()); // Initiate a mode change. mDesiredModeOpt = std::move(desiredMode); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 0966fe0496..7daeefe874 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -1028,6 +1028,8 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path, const Args& args) { + using InputConfig = gui::WindowInfo::InputConfig; + if (requested.windowInfoHandle) { snapshot.inputInfo = *requested.windowInfoHandle->getInfo(); } else { @@ -1056,6 +1058,11 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.dropInputMode = gui::DropInputMode::NONE; } + if (snapshot.isSecure || + parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_TRACING)) { + snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_TRACING; + } + updateVisibility(snapshot, snapshot.isVisible); if (!needsInputInfo(snapshot, requested)) { return; @@ -1068,14 +1075,14 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); if (!requested.windowInfoHandle) { - snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL; + snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL; } fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot); if (noValidDisplay) { // Do not let the window receive touches if it is not associated with a valid display // transform. We still allow the window to receive keys and prevent ANRs. - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE; + snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE; } snapshot.inputInfo.alpha = snapshot.color.a; @@ -1085,7 +1092,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // If the window will be blacked out on a display because the display does not have the secure // flag and the layer has the secure flag set, then drop input. if (!displayInfo.isSecure && snapshot.isSecure) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT; } if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) { @@ -1102,7 +1109,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state // if it was set by WM for a known system overlay if (snapshot.isTrustedOverlay) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY; + snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY; } snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize(); @@ -1110,10 +1117,10 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // If the layer is a clone, we need to crop the input region to cloned root to prevent // touches from going outside the cloned area. if (path.isClone()) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; + snapshot.inputInfo.inputConfig |= InputConfig::CLONE; // Cloned layers shouldn't handle watch outside since their z order is not determined by // WM or the client. - snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH); + snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH); } } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 867f3af4b1..028bd19a60 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -585,11 +585,13 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { return false; } - static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect | - layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | - layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged | - layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged | - layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent; + const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | + layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | + layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | + layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? 0 + : layer_state_t::eAutoRefreshChanged); if (s.what & deniedFlags) { ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 80eee15cea..073bad3c02 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3792,8 +3792,10 @@ bool Layer::isSimpleBufferUpdate(const layer_state_t& s) const { const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | - layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged | - layer_state_t::eReparent; + layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? 0 + : layer_state_t::eAutoRefreshChanged); if ((s.what & requiredFlags) != requiredFlags) { ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__, diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index a661292f9d..8ce61d86c6 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -68,6 +68,13 @@ public: void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex); + bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const EXCLUDES(mMutex) { + std::lock_guard lock(mMutex); + return mDisplayModePtr->getId() == modePtr->getId() && + mDisplayModePtr->getVsyncRate().getPeriodNsecs() == + mRateMap.find(idealPeriod())->second.slope; + } + void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex); void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index 9b8f310e16..8038364453 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -141,8 +141,7 @@ void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bo std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && - modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) { + if (!mSupportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 8787cdb82f..134d28e1e5 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -71,6 +71,11 @@ public: */ virtual Period minFramePeriod() const = 0; + /** + * Checks if the sourced mode is equal to the mode in the tracker. + */ + virtual bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const = 0; + /* Inform the tracker that the samples it has are not accurate for prediction. */ virtual void resetModel() = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 447435549b..949a030c1f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8295,10 +8295,12 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled ? mLayerSnapshotBuilder.getSnapshot(parent->sequence) : parent->getLayerSnapshot(); - display = findDisplay([layerStack = - snapshot->outputFilter.layerStack](const auto& display) { - return display.getLayerStack() == layerStack; - }).get(); + if (snapshot) { + display = findDisplay([layerStack = snapshot->outputFilter.layerStack]( + const auto& display) { + return display.getLayerStack() == layerStack; + }).get(); + } } if (display == nullptr) { diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index d9334d6855..12043d428e 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -140,6 +140,8 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(ce_fence_promise); DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout); DUMP_READ_ONLY_FLAG(graphite_renderengine); + DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed); + #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG #undef DUMP_FLAG_INTERVAL @@ -229,6 +231,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "") FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, ""); FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite") +FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 819e587f73..0239eb0d21 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -78,6 +78,7 @@ public: bool ce_fence_promise() const; bool idle_screen_refresh_rate_timeout() const; bool graphite_renderengine() const; + bool latch_unsignaled_with_auto_refresh_changed() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index a8fd6b7df7..0b9fd5887c 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -22,13 +22,6 @@ flag { } # ce_fence_promise flag { - name: "dont_skip_on_early_ro2" - namespace: "core_graphics" - description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early" - bug: "273702768" -} # dont_skip_on_early_ro2 - -flag { name: "frame_rate_category_mrr" namespace: "core_graphics" description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices" @@ -39,4 +32,15 @@ flag { } } # frame_rate_category_mrr +flag { + name: "latch_unsignaled_with_auto_refresh_changed" + namespace: "core_graphics" + description: "Ignore eAutoRefreshChanged with latch unsignaled" + bug: "331513837" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # latch_unsignaled_with_auto_refresh_changed + # IMPORTANT - please keep alphabetize to reduce merge conflicts diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 67e624922c..e8e7667a66 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -281,6 +281,24 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) { + std::vector<TransactionState> transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged; + transactions.back().states.front().layerId = id; + transactions.back().states.front().state.windowInfoHandle = + sp<gui::WindowInfoHandle>::make(); + auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo(); + if (!inputInfo->token) { + inputInfo->token = sp<BBinder>::make(); + } + configureInput(*inputInfo); + + mLifecycleManager.applyTransactions(transactions); + } + void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId, bool replaceTouchableRegionWithCrop) { std::vector<TransactionState> transactions; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 94989aac84..ae9a89c0b6 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1198,6 +1198,42 @@ TEST_F(LayerSnapshotTest, setSecureRootSnapshot) { EXPECT_TRUE(getSnapshot(11)->isSecure); } +TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) { + setFlags(11, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); + + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); +} + +TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) { + setInputInfo(11, [](auto& inputInfo) { + inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING; + }); + + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); +} + // b/314350323 TEST_F(LayerSnapshotTest, propagateDropInputMode) { setDropInputMode(1, gui::DropInputMode::ALL); diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 1f2a1edb77..7fb9247ee0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -17,6 +17,7 @@ #undef LOG_TAG #define LOG_TAG "TransactionApplicationTest" +#include <common/test/FlagUtils.h> #include <compositionengine/Display.h> #include <compositionengine/mock/DisplaySurface.h> #include <gmock/gmock.h> @@ -34,8 +35,11 @@ #include "TestableSurfaceFlinger.h" #include "TransactionState.h" +#include <com_android_graphics_surfaceflinger_flags.h> + namespace android { +using namespace com::android::graphics::surfaceflinger; using testing::_; using testing::Return; @@ -498,6 +502,44 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } +TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_AutoRefreshChanged) { + SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, false); + const sp<IBinder> kApplyToken = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const auto kLayerId = 1; + const auto kExpectedTransactionsPending = 1u; + + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken, + { + createComposerState(kLayerId, + fence(Fence::Status::Unsignaled), + layer_state_t::eAutoRefreshChanged | + layer_state_t:: + eBufferChanged), + }); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); +} + +TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesUnSignaledInTheQueue_AutoRefreshChanged) { + SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, true); + const sp<IBinder> kApplyToken = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const auto kLayerId = 1; + const auto kExpectedTransactionsPending = 0u; + + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken, + { + createComposerState(kLayerId, + fence(Fence::Status::Unsignaled), + layer_state_t::eAutoRefreshChanged | + layer_state_t:: + eBufferChanged), + }); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); +} + TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) { const sp<IBinder> kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index d701a97b7d..3b095545e9 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,6 +54,7 @@ public: void onFrameBegin(TimePoint, TimePoint) final {} void onFrameMissed(TimePoint) final {} void dump(std::string&) const final {} + bool isCurrentMode(const ftl::NonNull<DisplayModePtr>&) const final { return false; }; protected: std::mutex mutable mMutex; diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index e3aa4ef77b..51373c11f0 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -230,7 +230,8 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { nsecs_t sampleTime = 0; nsecs_t const newPeriod = 5000; - mReactor.onDisplayModeChanged(displayMode(newPeriod), false); + auto modePtr = displayMode(newPeriod); + mReactor.onDisplayModeChanged(modePtr, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -238,7 +239,9 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.onDisplayModeChanged(displayMode(period), false); + modePtr = displayMode(period); + EXPECT_CALL(*mMockTracker, isCurrentMode(modePtr)).WillOnce(Return(true)); + mReactor.onDisplayModeChanged(modePtr, false); EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); } diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index c311901c7a..4f44d1b4fc 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -40,6 +40,7 @@ public: MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); MOCK_METHOD(void, onFrameMissed, (TimePoint), (override)); MOCK_METHOD(void, dump, (std::string&), (const, override)); + MOCK_METHOD(bool, isCurrentMode, (const ftl::NonNull<DisplayModePtr>&), (const, override)); }; } // namespace android::mock |