1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "mocks/MockSprite.h"
18 #include "mocks/MockSpriteController.h"
19
20 #include <input/PointerController.h>
21 #include <input/SpriteController.h>
22
23 #include <atomic>
24 #include <gmock/gmock.h>
25 #include <gtest/gtest.h>
26 #include <thread>
27
28 namespace android {
29
30 enum TestCursorType {
31 CURSOR_TYPE_DEFAULT = 0,
32 CURSOR_TYPE_HOVER,
33 CURSOR_TYPE_TOUCH,
34 CURSOR_TYPE_ANCHOR,
35 CURSOR_TYPE_ADDITIONAL,
36 CURSOR_TYPE_ADDITIONAL_ANIM,
37 CURSOR_TYPE_CUSTOM = -1,
38 };
39
40 using ::testing::AllOf;
41 using ::testing::Field;
42 using ::testing::Mock;
43 using ::testing::NiceMock;
44 using ::testing::Return;
45 using ::testing::Test;
46
getHotSpotCoordinatesForType(int32_t type)47 std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) {
48 return std::make_pair(type * 10, type * 10 + 5);
49 }
50
51 class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
52 public:
53 virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
54 virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
55 virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
56 std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
57 virtual int32_t getDefaultPointerIconId() override;
58 virtual int32_t getCustomPointerIconId() override;
59 virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
60
61 bool allResourcesAreLoaded();
62 bool noResourcesAreLoaded();
getLastReportedPointerDisplayId()63 std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
64
65 private:
66 void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
67
68 bool pointerIconLoaded{false};
69 bool pointerResourcesLoaded{false};
70 bool additionalMouseResourcesLoaded{false};
71 std::optional<int32_t /*displayId*/> latestPointerDisplayId;
72 };
73
loadPointerIcon(SpriteIcon * icon,int32_t)74 void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
75 loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
76 pointerIconLoaded = true;
77 }
78
loadPointerResources(PointerResources * outResources,int32_t)79 void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
80 int32_t) {
81 loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
82 loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
83 loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
84 pointerResourcesLoaded = true;
85 }
86
loadAdditionalMouseResources(std::map<int32_t,SpriteIcon> * outResources,std::map<int32_t,PointerAnimation> * outAnimationResources,int32_t)87 void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
88 std::map<int32_t, SpriteIcon>* outResources,
89 std::map<int32_t, PointerAnimation>* outAnimationResources,
90 int32_t) {
91 SpriteIcon icon;
92 PointerAnimation anim;
93
94 // CURSOR_TYPE_ADDITIONAL doesn't have animation resource.
95 int32_t cursorType = CURSOR_TYPE_ADDITIONAL;
96 loadPointerIconForType(&icon, cursorType);
97 (*outResources)[cursorType] = icon;
98
99 // CURSOR_TYPE_ADDITIONAL_ANIM has animation resource.
100 cursorType = CURSOR_TYPE_ADDITIONAL_ANIM;
101 loadPointerIconForType(&icon, cursorType);
102 anim.animationFrames.push_back(icon);
103 anim.durationPerFrame = 10;
104 (*outResources)[cursorType] = icon;
105 (*outAnimationResources)[cursorType] = anim;
106
107 additionalMouseResourcesLoaded = true;
108 }
109
getDefaultPointerIconId()110 int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
111 return CURSOR_TYPE_DEFAULT;
112 }
113
getCustomPointerIconId()114 int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() {
115 return CURSOR_TYPE_CUSTOM;
116 }
117
allResourcesAreLoaded()118 bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() {
119 return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded;
120 }
121
noResourcesAreLoaded()122 bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() {
123 return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded);
124 }
125
loadPointerIconForType(SpriteIcon * icon,int32_t type)126 void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) {
127 icon->style = type;
128 std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type);
129 icon->hotSpotX = hotSpot.first;
130 icon->hotSpotY = hotSpot.second;
131 }
132
onPointerDisplayIdChanged(int32_t displayId,float,float)133 void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
134 float /*xPos*/,
135 float /*yPos*/) {
136 latestPointerDisplayId = displayId;
137 }
138
139 class PointerControllerTest : public Test {
140 protected:
141 PointerControllerTest();
142 ~PointerControllerTest();
143
144 void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
145
146 sp<MockSprite> mPointerSprite;
147 sp<MockPointerControllerPolicyInterface> mPolicy;
148 sp<MockSpriteController> mSpriteController;
149 std::shared_ptr<PointerController> mPointerController;
150
151 private:
152 void loopThread();
153
154 std::atomic<bool> mRunning = true;
155 class MyLooper : public Looper {
156 public:
MyLooper()157 MyLooper() : Looper(false) {}
158 ~MyLooper() = default;
159 };
160 sp<MyLooper> mLooper;
161 std::thread mThread;
162 };
163
PointerControllerTest()164 PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
165 mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
166
167 mSpriteController = new NiceMock<MockSpriteController>(mLooper);
168 mPolicy = new MockPointerControllerPolicyInterface();
169
170 EXPECT_CALL(*mSpriteController, createSprite())
171 .WillOnce(Return(mPointerSprite));
172
173 mPointerController = PointerController::create(mPolicy, mLooper, mSpriteController);
174 }
175
~PointerControllerTest()176 PointerControllerTest::~PointerControllerTest() {
177 mRunning.store(false, std::memory_order_relaxed);
178 mThread.join();
179 }
180
ensureDisplayViewportIsSet(int32_t displayId)181 void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
182 DisplayViewport viewport;
183 viewport.displayId = displayId;
184 viewport.logicalRight = 1600;
185 viewport.logicalBottom = 1200;
186 viewport.physicalRight = 800;
187 viewport.physicalBottom = 600;
188 viewport.deviceWidth = 400;
189 viewport.deviceHeight = 300;
190 mPointerController->setDisplayViewport(viewport);
191 }
192
loopThread()193 void PointerControllerTest::loopThread() {
194 Looper::setForThread(mLooper);
195
196 while (mRunning.load(std::memory_order_relaxed)) {
197 mLooper->pollOnce(100);
198 }
199 }
200
TEST_F(PointerControllerTest,useDefaultCursorTypeByDefault)201 TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
202 ensureDisplayViewportIsSet();
203 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
204
205 std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT);
206 EXPECT_CALL(*mPointerSprite, setVisible(true));
207 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
208 EXPECT_CALL(*mPointerSprite, setIcon(
209 AllOf(
210 Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT),
211 Field(&SpriteIcon::hotSpotX, hotspot.first),
212 Field(&SpriteIcon::hotSpotY, hotspot.second))));
213 mPointerController->reloadPointerResources();
214 }
215
TEST_F(PointerControllerTest,updatePointerIcon)216 TEST_F(PointerControllerTest, updatePointerIcon) {
217 ensureDisplayViewportIsSet();
218 mPointerController->setPresentation(PointerController::Presentation::POINTER);
219 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
220
221 int32_t type = CURSOR_TYPE_ADDITIONAL;
222 std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
223 EXPECT_CALL(*mPointerSprite, setVisible(true));
224 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
225 EXPECT_CALL(*mPointerSprite, setIcon(
226 AllOf(
227 Field(&SpriteIcon::style, type),
228 Field(&SpriteIcon::hotSpotX, hotspot.first),
229 Field(&SpriteIcon::hotSpotY, hotspot.second))));
230 mPointerController->updatePointerIcon(type);
231 }
232
TEST_F(PointerControllerTest,setCustomPointerIcon)233 TEST_F(PointerControllerTest, setCustomPointerIcon) {
234 ensureDisplayViewportIsSet();
235 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
236
237 int32_t style = CURSOR_TYPE_CUSTOM;
238 float hotSpotX = 15;
239 float hotSpotY = 20;
240
241 SpriteIcon icon;
242 icon.style = style;
243 icon.hotSpotX = hotSpotX;
244 icon.hotSpotY = hotSpotY;
245
246 EXPECT_CALL(*mPointerSprite, setVisible(true));
247 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
248 EXPECT_CALL(*mPointerSprite, setIcon(
249 AllOf(
250 Field(&SpriteIcon::style, style),
251 Field(&SpriteIcon::hotSpotX, hotSpotX),
252 Field(&SpriteIcon::hotSpotY, hotSpotY))));
253 mPointerController->setCustomPointerIcon(icon);
254 }
255
TEST_F(PointerControllerTest,doesNotGetResourcesBeforeSettingViewport)256 TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
257 mPointerController->setPresentation(PointerController::Presentation::POINTER);
258 mPointerController->setPosition(1.0f, 1.0f);
259 mPointerController->move(1.0f, 1.0f);
260 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
261 mPointerController->fade(PointerController::Transition::IMMEDIATE);
262
263 EXPECT_TRUE(mPolicy->noResourcesAreLoaded());
264
265 ensureDisplayViewportIsSet();
266 }
267
TEST_F(PointerControllerTest,notifiesPolicyWhenPointerDisplayChanges)268 TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
269 EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
270 << "A pointer display change does not occur when PointerController is created.";
271
272 ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
273
274 const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
275 ASSERT_TRUE(lastReportedPointerDisplayId)
276 << "The policy is notified of a pointer display change when the viewport is first set.";
277 EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
278 << "Incorrect pointer display notified.";
279
280 ensureDisplayViewportIsSet(42);
281
282 EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
283 << "The policy is notified when the pointer display changes.";
284
285 // Release the PointerController.
286 mPointerController = nullptr;
287
288 EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
289 << "The pointer display changes to invalid when PointerController is destroyed.";
290 }
291
292 class PointerControllerWindowInfoListenerTest : public Test {};
293
294 class TestPointerController : public PointerController {
295 public:
TestPointerController(sp<android::gui::WindowInfosListener> & registeredListener,const sp<Looper> & looper)296 TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
297 const sp<Looper>& looper)
298 : PointerController(
299 new MockPointerControllerPolicyInterface(), looper,
300 new NiceMock<MockSpriteController>(looper),
301 [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
302 // Register listener
303 registeredListener = listener;
304 },
__anon4ef577b40202(const sp<android::gui::WindowInfosListener>& listener) 305 [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
306 // Unregister listener
307 if (registeredListener == listener) registeredListener = nullptr;
308 }) {}
309 };
310
TEST_F(PointerControllerWindowInfoListenerTest,doesNotCrashIfListenerCalledAfterPointerControllerDestroyed)311 TEST_F(PointerControllerWindowInfoListenerTest,
312 doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
313 sp<android::gui::WindowInfosListener> registeredListener;
314 sp<android::gui::WindowInfosListener> localListenerCopy;
315 {
316 TestPointerController pointerController(registeredListener, new Looper(false));
317 ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
318 localListenerCopy = registeredListener;
319 }
320 EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
321 localListenerCopy->onWindowInfosChanged({}, {});
322 }
323
324 } // namespace android
325