| // 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/window_android.h" |
| #include "ui/events/android/event_handler_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(ViewAndroid::LayoutType layout_type) |
| : ViewAndroid(layout_type) {} |
| |
| float GetDipScale() override { return 1.f; } |
| }; |
| |
| class TestEventHandler : public EventHandlerAndroid { |
| public: |
| TestEventHandler() {} |
| |
| 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_(ViewAndroid::LayoutType::MATCH_PARENT), |
| view1_(ViewAndroid::LayoutType::NORMAL), |
| view2_(ViewAndroid::LayoutType::NORMAL), |
| view3_(ViewAndroid::LayoutType::NORMAL), |
| viewm_(ViewAndroid::LayoutType::MATCH_PARENT) { |
| root_.GetEventForwarder(); |
| view1_.set_event_handler(&handler1_); |
| view2_.set_event_handler(&handler2_); |
| view3_.set_event_handler(&handler3_); |
| viewm_.set_event_handler(&handlerm_); |
| } |
| |
| void Reset() { |
| handler1_.Reset(); |
| handler2_.Reset(); |
| handler3_.Reset(); |
| handlerm_.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<jobject>(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 TestEventHandler& hitHandler) { |
| TestEventHandler* handlers[4] = {&handler1_, &handler2_, &handler3_, |
| &handlerm_}; |
| for (auto* handler : handlers) { |
| if (&hitHandler == handler) |
| EXPECT_TRUE(handler->TouchEventHandled()); |
| else |
| EXPECT_FALSE(handler->TouchEventHandled()); |
| } |
| Reset(); |
| } |
| |
| TestViewAndroid root_; |
| TestViewAndroid view1_; |
| TestViewAndroid view2_; |
| TestViewAndroid view3_; |
| TestViewAndroid viewm_; // match-parent view |
| TestEventHandler handler1_; |
| TestEventHandler handler2_; |
| TestEventHandler handler3_; |
| TestEventHandler handlerm_; |
| 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(handler1_); |
| |
| // View 2 moves up to the top, and events should hit it from now. |
| root_.MoveToFront(&view2_); |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler2_); |
| } |
| |
| 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(handler1_); |
| |
| // Falls within |view2_|'s bounds |
| GenerateTouchEventAt(300.f, 400.f); |
| ExpectHit(handler2_); |
| } |
| |
| 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(handler1_); |
| |
| view1_.SetLayoutForTesting(150, 150, 200, 200); |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler2_); |
| } |
| |
| 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(handler2_); |
| |
| GenerateTouchEventAt(300.f, 400.f); |
| ExpectHit(handler1_); |
| |
| handler1_.SetHandleEvent(false); |
| GenerateTouchEventAt(300.f, 400.f); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handlerm_); |
| } |
| |
| 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(handler1_); |
| |
| handler1_.SetHandleEvent(false); |
| GenerateTouchEventAt(40, 60); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handler2_); |
| |
| GenerateTouchEventAt(100, 70); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handler3_); |
| } |
| |
| 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(handler1_.OnSizeCalled()); |
| EXPECT_TRUE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.OnSizeCalled()); |
| |
| Reset(); |
| |
| // Match-parent view should not receivee size events in the first place. |
| EXPECT_DCHECK_DEATH(viewm_.OnSizeChanged(100, 200)); |
| EXPECT_FALSE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.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(handlerm_.OnSizeCalled()); |
| view1_.AddChild(&viewm_); |
| EXPECT_TRUE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.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(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.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<WindowAndroid> 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 |