// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/test/gtest_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/android/event_forwarder.h" #include "ui/android/view_android.h" #include "ui/android/view_android_observer.h" #include "ui/android/view_client.h" #include "ui/android/window_android.h" #include "ui/events/android/motion_event_android.h" #include "ui/events/test/scoped_event_test_tick_clock.h" namespace ui { using base::android::JavaParamRef; class TestViewAndroid : public ViewAndroid { public: TestViewAndroid(ViewClient* client, ViewAndroid::LayoutType layout_type) : ViewAndroid(client, layout_type) {} float GetDipScale() override { return 1.f; } }; class TestViewClient : public ViewClient { public: TestViewClient() {} bool OnTouchEvent(const MotionEventAndroid& event) override { touch_called_ = true; return handle_event_; } void OnSizeChanged() override { onsize_called_ = true; } void SetHandleEvent(bool handle_event) { handle_event_ = handle_event; } bool TouchEventHandled() { return touch_called_ && handle_event_; } bool TouchEventCalled() { return touch_called_; } bool OnSizeCalled() { return onsize_called_; } void Reset() { touch_called_ = false; onsize_called_ = false; } private: bool handle_event_{true}; // Marks as event was consumed. True by default. bool touch_called_{false}; bool onsize_called_{false}; }; class ViewAndroidBoundsTest : public testing::Test { public: ViewAndroidBoundsTest() : root_(nullptr, ViewAndroid::LayoutType::MATCH_PARENT), view1_(&client1_, ViewAndroid::LayoutType::NORMAL), view2_(&client2_, ViewAndroid::LayoutType::NORMAL), view3_(&client3_, ViewAndroid::LayoutType::NORMAL), viewm_(&clientm_, ViewAndroid::LayoutType::MATCH_PARENT) { root_.GetEventForwarder(); } void Reset() { client1_.Reset(); client2_.Reset(); client3_.Reset(); clientm_.Reset(); test_clock_.SetNowTicks(base::TimeTicks()); } void GenerateTouchEventAt(float x, float y) { ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0); ui::MotionEventAndroid::Pointer pointer1(0, 0, 0, 0, 0, 0, 0, 0); ui::MotionEventAndroid event(nullptr, JavaParamRef(nullptr), 1.f, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, false, &pointer0, &pointer1); root_.OnTouchEvent(event); } void ExpectHit(const TestViewClient& hitClient) { TestViewClient* clients[4] = {&client1_, &client2_, &client3_, &clientm_}; for (auto* client : clients) { if (&hitClient == client) EXPECT_TRUE(client->TouchEventHandled()); else EXPECT_FALSE(client->TouchEventHandled()); } Reset(); } TestViewAndroid root_; TestViewAndroid view1_; TestViewAndroid view2_; TestViewAndroid view3_; TestViewAndroid viewm_; // match-parent view TestViewClient client1_; TestViewClient client2_; TestViewClient client3_; TestViewClient clientm_; ui::test::ScopedEventTestTickClock test_clock_; }; TEST_F(ViewAndroidBoundsTest, MatchesViewInFront) { view1_.SetLayoutForTesting(50, 50, 400, 600); view2_.SetLayoutForTesting(50, 50, 400, 600); root_.AddChild(&view2_); root_.AddChild(&view1_); GenerateTouchEventAt(100.f, 100.f); ExpectHit(client1_); // View 2 moves up to the top, and events should hit it from now. root_.MoveToFront(&view2_); GenerateTouchEventAt(100.f, 100.f); ExpectHit(client2_); } TEST_F(ViewAndroidBoundsTest, MatchesViewArea) { view1_.SetLayoutForTesting(50, 50, 200, 200); view2_.SetLayoutForTesting(20, 20, 400, 600); root_.AddChild(&view2_); root_.AddChild(&view1_); // Falls within |view1_|'s bounds GenerateTouchEventAt(100.f, 100.f); ExpectHit(client1_); // Falls within |view2_|'s bounds GenerateTouchEventAt(300.f, 400.f); ExpectHit(client2_); } TEST_F(ViewAndroidBoundsTest, MatchesViewAfterMove) { view1_.SetLayoutForTesting(50, 50, 200, 200); view2_.SetLayoutForTesting(20, 20, 400, 600); root_.AddChild(&view2_); root_.AddChild(&view1_); GenerateTouchEventAt(100.f, 100.f); ExpectHit(client1_); view1_.SetLayoutForTesting(150, 150, 200, 200); GenerateTouchEventAt(100.f, 100.f); ExpectHit(client2_); } TEST_F(ViewAndroidBoundsTest, MatchesViewSizeOfkMatchParent) { view1_.SetLayoutForTesting(20, 20, 400, 600); view2_.SetLayoutForTesting(50, 50, 200, 200); root_.AddChild(&view1_); root_.AddChild(&view2_); view1_.AddChild(&viewm_); GenerateTouchEventAt(100.f, 100.f); ExpectHit(client2_); GenerateTouchEventAt(300.f, 400.f); ExpectHit(client1_); client1_.SetHandleEvent(false); GenerateTouchEventAt(300.f, 400.f); EXPECT_TRUE(client1_.TouchEventCalled()); ExpectHit(clientm_); } TEST_F(ViewAndroidBoundsTest, MatchesViewsWithOffset) { view1_.SetLayoutForTesting(10, 20, 150, 100); view2_.SetLayoutForTesting(20, 30, 40, 30); view3_.SetLayoutForTesting(70, 30, 40, 30); root_.AddChild(&view1_); view1_.AddChild(&view2_); view1_.AddChild(&view3_); GenerateTouchEventAt(70, 30); ExpectHit(client1_); client1_.SetHandleEvent(false); GenerateTouchEventAt(40, 60); EXPECT_TRUE(client1_.TouchEventCalled()); ExpectHit(client2_); GenerateTouchEventAt(100, 70); EXPECT_TRUE(client1_.TouchEventCalled()); ExpectHit(client3_); } TEST_F(ViewAndroidBoundsTest, OnSizeChanged) { root_.AddChild(&view1_); view1_.AddChild(&viewm_); view1_.AddChild(&view3_); // Size event propagates to non-match-parent children only. view1_.OnSizeChanged(100, 100); EXPECT_TRUE(client1_.OnSizeCalled()); EXPECT_TRUE(clientm_.OnSizeCalled()); EXPECT_FALSE(client3_.OnSizeCalled()); Reset(); // Match-parent view should not receivee size events in the first place. EXPECT_DCHECK_DEATH(viewm_.OnSizeChanged(100, 200)); EXPECT_FALSE(clientm_.OnSizeCalled()); EXPECT_FALSE(client3_.OnSizeCalled()); viewm_.RemoveFromParent(); viewm_.OnSizeChangedInternal(gfx::Size(0, 0)); // Reset the size. Reset(); view1_.OnSizeChanged(100, 100); // Size event is generated for a newly added, match-parent child view. EXPECT_FALSE(clientm_.OnSizeCalled()); view1_.AddChild(&viewm_); EXPECT_TRUE(clientm_.OnSizeCalled()); EXPECT_FALSE(client3_.OnSizeCalled()); viewm_.RemoveFromParent(); Reset(); view1_.OnSizeChanged(100, 100); // Size event won't propagate if the children already have the same size. view1_.AddChild(&viewm_); EXPECT_FALSE(clientm_.OnSizeCalled()); EXPECT_FALSE(client3_.OnSizeCalled()); } TEST(ViewAndroidTest, ChecksMultipleEventForwarders) { ViewAndroid parent; ViewAndroid child; parent.GetEventForwarder(); child.GetEventForwarder(); EXPECT_DCHECK_DEATH(parent.AddChild(&child)); ViewAndroid parent2; ViewAndroid child2; parent2.GetEventForwarder(); parent2.AddChild(&child2); EXPECT_DCHECK_DEATH(child2.GetEventForwarder()); ViewAndroid window; ViewAndroid wcv1, wcv2; ViewAndroid rwhv1a, rwhv1b, rwhv2; wcv1.GetEventForwarder(); wcv2.GetEventForwarder(); window.AddChild(&wcv1); wcv1.AddChild(&rwhv1a); wcv1.AddChild(&rwhv1b); wcv2.AddChild(&rwhv2); // window should be able to add wcv2 since there's only one event forwarder // in the path window - wcv2* - rwvh2 window.AddChild(&wcv2); // Additional event forwarder will cause failure. EXPECT_DCHECK_DEATH(rwhv2.GetEventForwarder()); } class Observer : public ViewAndroidObserver { public: Observer() : attached_(false) {} void OnAttachedToWindow() override { attached_ = true; } void OnDetachedFromWindow() override { attached_ = false; } bool attached_; }; TEST(ViewAndroidTest, Observer) { std::unique_ptr window(WindowAndroid::CreateForTesting()); ViewAndroid top; ViewAndroid bottom; Observer top_observer; Observer bottom_observer; top.AddObserver(&top_observer); bottom.AddObserver(&bottom_observer); top.AddChild(&bottom); EXPECT_FALSE(top_observer.attached_); EXPECT_FALSE(bottom_observer.attached_); // Views in a tree all get notified of 'attached' event. window->AddChild(&top); EXPECT_TRUE(top_observer.attached_); EXPECT_TRUE(bottom_observer.attached_); // Observer, upon addition, does not get notified of the current // attached state. Observer top_observer2; top.AddObserver(&top_observer2); EXPECT_FALSE(top_observer2.attached_); bottom.RemoveFromParent(); EXPECT_FALSE(bottom_observer.attached_); top.RemoveFromParent(); EXPECT_FALSE(top_observer.attached_); window->AddChild(&top); EXPECT_TRUE(top_observer.attached_); // View, upon addition to a tree in the attached state, should be notified. top.AddChild(&bottom); EXPECT_TRUE(bottom_observer.attached_); // Views in a tree all get notified of 'detached' event. top.RemoveFromParent(); EXPECT_FALSE(top_observer.attached_); EXPECT_FALSE(bottom_observer.attached_); } } // namespace ui