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