| // 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<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 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<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 |