blob: 3bc0e24b6e2efc4991a038875d57797712bcd635 [file] [log] [blame]
Garfield Tanc15eb912019-08-05 16:47:40 -07001/*
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
Prabir Pradhan6c7aa6f2023-12-12 16:54:59 +000017#include <flag_macros.h>
Brandon Pollack015f5d92022-06-02 06:59:33 +000018#include <gmock/gmock.h>
19#include <gtest/gtest.h>
Garfield Tanc15eb912019-08-05 16:47:40 -070020#include <input/PointerController.h>
21#include <input/SpriteController.h>
22
23#include <atomic>
Garfield Tanc15eb912019-08-05 16:47:40 -070024#include <thread>
25
Brandon Pollack015f5d92022-06-02 06:59:33 +000026#include "input/Input.h"
27#include "mocks/MockSprite.h"
28#include "mocks/MockSpriteController.h"
29
Garfield Tanc15eb912019-08-05 16:47:40 -070030namespace android {
31
32enum TestCursorType {
33 CURSOR_TYPE_DEFAULT = 0,
34 CURSOR_TYPE_HOVER,
35 CURSOR_TYPE_TOUCH,
36 CURSOR_TYPE_ANCHOR,
Garfield Tane9c61512019-11-21 16:42:13 -080037 CURSOR_TYPE_ADDITIONAL,
38 CURSOR_TYPE_ADDITIONAL_ANIM,
Seunghwan Choi670b33d2023-01-13 21:12:59 +090039 CURSOR_TYPE_STYLUS,
Garfield Tanc15eb912019-08-05 16:47:40 -070040 CURSOR_TYPE_CUSTOM = -1,
41};
42
43using ::testing::AllOf;
44using ::testing::Field;
Prabir Pradhanca7d7232020-01-31 17:42:34 -080045using ::testing::NiceMock;
Garfield Tanc15eb912019-08-05 16:47:40 -070046using ::testing::Return;
47using ::testing::Test;
48
49std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) {
50 return std::make_pair(type * 10, type * 10 + 5);
51}
52
53class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
54public:
55 virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
56 virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
Brandon Pollack015f5d92022-06-02 06:59:33 +000057 virtual void loadAdditionalMouseResources(
58 std::map<PointerIconStyle, SpriteIcon>* outResources,
59 std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
60 int32_t displayId) override;
61 virtual PointerIconStyle getDefaultPointerIconId() override;
Seunghwan Choi670b33d2023-01-13 21:12:59 +090062 virtual PointerIconStyle getDefaultStylusIconId() override;
Brandon Pollack015f5d92022-06-02 06:59:33 +000063 virtual PointerIconStyle getCustomPointerIconId() override;
Garfield Tanc15eb912019-08-05 16:47:40 -070064
Prabir Pradhanca7d7232020-01-31 17:42:34 -080065 bool allResourcesAreLoaded();
66 bool noResourcesAreLoaded();
67
Garfield Tanc15eb912019-08-05 16:47:40 -070068private:
69 void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
Prabir Pradhanca7d7232020-01-31 17:42:34 -080070
71 bool pointerIconLoaded{false};
72 bool pointerResourcesLoaded{false};
73 bool additionalMouseResourcesLoaded{false};
Garfield Tanc15eb912019-08-05 16:47:40 -070074};
75
76void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
77 loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
Prabir Pradhanca7d7232020-01-31 17:42:34 -080078 pointerIconLoaded = true;
Garfield Tanc15eb912019-08-05 16:47:40 -070079}
80
81void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
82 int32_t) {
83 loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
84 loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
85 loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
Prabir Pradhanca7d7232020-01-31 17:42:34 -080086 pointerResourcesLoaded = true;
Garfield Tanc15eb912019-08-05 16:47:40 -070087}
88
89void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
Brandon Pollack015f5d92022-06-02 06:59:33 +000090 std::map<PointerIconStyle, SpriteIcon>* outResources,
91 std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
Garfield Tanc15eb912019-08-05 16:47:40 -070092 SpriteIcon icon;
93 PointerAnimation anim;
94
Garfield Tane9c61512019-11-21 16:42:13 -080095 // CURSOR_TYPE_ADDITIONAL doesn't have animation resource.
96 int32_t cursorType = CURSOR_TYPE_ADDITIONAL;
97 loadPointerIconForType(&icon, cursorType);
Brandon Pollack015f5d92022-06-02 06:59:33 +000098 (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
Garfield Tane9c61512019-11-21 16:42:13 -080099
100 // CURSOR_TYPE_ADDITIONAL_ANIM has animation resource.
101 cursorType = CURSOR_TYPE_ADDITIONAL_ANIM;
102 loadPointerIconForType(&icon, cursorType);
103 anim.animationFrames.push_back(icon);
104 anim.durationPerFrame = 10;
Brandon Pollack015f5d92022-06-02 06:59:33 +0000105 (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
106 (*outAnimationResources)[static_cast<PointerIconStyle>(cursorType)] = anim;
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800107
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900108 // CURSOR_TYPE_STYLUS doesn't have animation resource.
109 cursorType = CURSOR_TYPE_STYLUS;
110 loadPointerIconForType(&icon, cursorType);
111 (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
112
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800113 additionalMouseResourcesLoaded = true;
Garfield Tanc15eb912019-08-05 16:47:40 -0700114}
115
Brandon Pollack015f5d92022-06-02 06:59:33 +0000116PointerIconStyle MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
117 return static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT);
Garfield Tanc15eb912019-08-05 16:47:40 -0700118}
119
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900120PointerIconStyle MockPointerControllerPolicyInterface::getDefaultStylusIconId() {
121 return static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS);
122}
123
Brandon Pollack015f5d92022-06-02 06:59:33 +0000124PointerIconStyle MockPointerControllerPolicyInterface::getCustomPointerIconId() {
125 return static_cast<PointerIconStyle>(CURSOR_TYPE_CUSTOM);
Garfield Tanc15eb912019-08-05 16:47:40 -0700126}
127
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800128bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() {
129 return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded;
130}
131
132bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() {
133 return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded);
134}
135
Garfield Tanc15eb912019-08-05 16:47:40 -0700136void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) {
Brandon Pollack015f5d92022-06-02 06:59:33 +0000137 icon->style = static_cast<PointerIconStyle>(type);
Garfield Tanc15eb912019-08-05 16:47:40 -0700138 std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type);
139 icon->hotSpotX = hotSpot.first;
140 icon->hotSpotY = hotSpot.second;
141}
Prabir Pradhan0e3d6652022-03-10 14:39:46 +0000142
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700143class TestPointerController : public PointerController {
144public:
145 TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
146 sp<PointerControllerPolicyInterface> policy, const sp<Looper>& looper,
147 SpriteController& spriteController)
148 : PointerController(
149 policy, looper, spriteController,
Linnan Li37c1b992023-11-24 13:05:13 +0800150 [&registeredListener](const sp<android::gui::WindowInfosListener>& listener)
Prabir Pradhanbdf93692024-01-23 18:08:28 +0000151 -> std::vector<gui::DisplayInfo> {
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700152 // Register listener
153 registeredListener = listener;
Linnan Li37c1b992023-11-24 13:05:13 +0800154 return {};
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700155 },
156 [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
157 // Unregister listener
158 if (registeredListener == listener) registeredListener = nullptr;
159 }) {}
160 ~TestPointerController() override {}
161};
162
Garfield Tanc15eb912019-08-05 16:47:40 -0700163class PointerControllerTest : public Test {
164protected:
165 PointerControllerTest();
166 ~PointerControllerTest();
167
Prabir Pradhan0e3d6652022-03-10 14:39:46 +0000168 void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800169
Garfield Tanc15eb912019-08-05 16:47:40 -0700170 sp<MockSprite> mPointerSprite;
171 sp<MockPointerControllerPolicyInterface> mPolicy;
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000172 std::unique_ptr<MockSpriteController> mSpriteController;
Michael Wrighta0bc6b12020-06-26 20:25:34 +0100173 std::shared_ptr<PointerController> mPointerController;
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700174 sp<android::gui::WindowInfosListener> mRegisteredListener;
Garfield Tanc15eb912019-08-05 16:47:40 -0700175
176private:
177 void loopThread();
178
179 std::atomic<bool> mRunning = true;
180 class MyLooper : public Looper {
181 public:
182 MyLooper() : Looper(false) {}
183 ~MyLooper() = default;
184 };
185 sp<MyLooper> mLooper;
186 std::thread mThread;
187};
188
189PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
190 mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000191 mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper));
Garfield Tanc15eb912019-08-05 16:47:40 -0700192 mPolicy = new MockPointerControllerPolicyInterface();
193
194 EXPECT_CALL(*mSpriteController, createSprite())
195 .WillOnce(Return(mPointerSprite));
196
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700197 mPointerController = std::make_unique<TestPointerController>(mRegisteredListener, mPolicy,
198 mLooper, *mSpriteController);
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800199}
Garfield Tanc15eb912019-08-05 16:47:40 -0700200
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800201PointerControllerTest::~PointerControllerTest() {
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700202 mPointerController.reset();
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800203 mRunning.store(false, std::memory_order_relaxed);
204 mThread.join();
205}
206
Prabir Pradhan0e3d6652022-03-10 14:39:46 +0000207void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
Garfield Tanc15eb912019-08-05 16:47:40 -0700208 DisplayViewport viewport;
Prabir Pradhan0e3d6652022-03-10 14:39:46 +0000209 viewport.displayId = displayId;
Garfield Tanc15eb912019-08-05 16:47:40 -0700210 viewport.logicalRight = 1600;
211 viewport.logicalBottom = 1200;
212 viewport.physicalRight = 800;
213 viewport.physicalBottom = 600;
214 viewport.deviceWidth = 400;
215 viewport.deviceHeight = 300;
216 mPointerController->setDisplayViewport(viewport);
Garfield Tanc15eb912019-08-05 16:47:40 -0700217}
218
219void PointerControllerTest::loopThread() {
220 Looper::setForThread(mLooper);
221
222 while (mRunning.load(std::memory_order_relaxed)) {
223 mLooper->pollOnce(100);
224 }
225}
226
227TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800228 ensureDisplayViewportIsSet();
Michael Wright6853fe62020-07-02 00:01:38 +0100229 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
Garfield Tanc15eb912019-08-05 16:47:40 -0700230
231 std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT);
232 EXPECT_CALL(*mPointerSprite, setVisible(true));
233 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
Brandon Pollack015f5d92022-06-02 06:59:33 +0000234 EXPECT_CALL(*mPointerSprite,
235 setIcon(AllOf(Field(&SpriteIcon::style,
236 static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT)),
237 Field(&SpriteIcon::hotSpotX, hotspot.first),
238 Field(&SpriteIcon::hotSpotY, hotspot.second))));
Garfield Tanc15eb912019-08-05 16:47:40 -0700239 mPointerController->reloadPointerResources();
240}
241
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900242TEST_F(PointerControllerTest, useStylusTypeForStylusHover) {
243 ensureDisplayViewportIsSet();
244 mPointerController->setPresentation(PointerController::Presentation::STYLUS_HOVER);
245 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
246 std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_STYLUS);
247 EXPECT_CALL(*mPointerSprite, setVisible(true));
248 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
249 EXPECT_CALL(*mPointerSprite,
250 setIcon(AllOf(Field(&SpriteIcon::style,
251 static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS)),
252 Field(&SpriteIcon::hotSpotX, hotspot.first),
253 Field(&SpriteIcon::hotSpotY, hotspot.second))));
254 mPointerController->reloadPointerResources();
255}
256
Prabir Pradhan7dff1422024-05-03 23:33:28 +0000257TEST_F(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources) {
Prabir Pradhan6c7aa6f2023-12-12 16:54:59 +0000258 // Setting the presentation mode before a display viewport is set will not load any resources.
259 mPointerController->setPresentation(PointerController::Presentation::POINTER);
260 ASSERT_TRUE(mPolicy->noResourcesAreLoaded());
261
262 // When the display is set, then the resources are loaded.
263 ensureDisplayViewportIsSet();
264 ASSERT_TRUE(mPolicy->allResourcesAreLoaded());
265}
266
Prabir Pradhan7dff1422024-05-03 23:33:28 +0000267TEST_F(PointerControllerTest, updatePointerIconWithChoreographer) {
Prabir Pradhan6c7aa6f2023-12-12 16:54:59 +0000268 // When PointerChoreographer is enabled, the presentation mode is set before the viewport.
269 mPointerController->setPresentation(PointerController::Presentation::POINTER);
270 ensureDisplayViewportIsSet();
271 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
272
273 int32_t type = CURSOR_TYPE_ADDITIONAL;
274 std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
275 EXPECT_CALL(*mPointerSprite, setVisible(true));
276 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
277 EXPECT_CALL(*mPointerSprite,
278 setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
279 Field(&SpriteIcon::hotSpotX, hotspot.first),
280 Field(&SpriteIcon::hotSpotY, hotspot.second))));
281 mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
282}
283
Garfield Tanc15eb912019-08-05 16:47:40 -0700284TEST_F(PointerControllerTest, setCustomPointerIcon) {
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800285 ensureDisplayViewportIsSet();
Michael Wright6853fe62020-07-02 00:01:38 +0100286 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
Garfield Tanc15eb912019-08-05 16:47:40 -0700287
288 int32_t style = CURSOR_TYPE_CUSTOM;
289 float hotSpotX = 15;
290 float hotSpotY = 20;
291
292 SpriteIcon icon;
Brandon Pollack015f5d92022-06-02 06:59:33 +0000293 icon.style = static_cast<PointerIconStyle>(style);
Garfield Tanc15eb912019-08-05 16:47:40 -0700294 icon.hotSpotX = hotSpotX;
295 icon.hotSpotY = hotSpotY;
296
297 EXPECT_CALL(*mPointerSprite, setVisible(true));
298 EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
Brandon Pollack015f5d92022-06-02 06:59:33 +0000299 EXPECT_CALL(*mPointerSprite,
300 setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(style)),
301 Field(&SpriteIcon::hotSpotX, hotSpotX),
302 Field(&SpriteIcon::hotSpotY, hotSpotY))));
Garfield Tanc15eb912019-08-05 16:47:40 -0700303 mPointerController->setCustomPointerIcon(icon);
304}
305
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800306TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
Michael Wright6853fe62020-07-02 00:01:38 +0100307 mPointerController->setPresentation(PointerController::Presentation::POINTER);
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800308 mPointerController->setPosition(1.0f, 1.0f);
309 mPointerController->move(1.0f, 1.0f);
Michael Wright6853fe62020-07-02 00:01:38 +0100310 mPointerController->unfade(PointerController::Transition::IMMEDIATE);
311 mPointerController->fade(PointerController::Transition::IMMEDIATE);
Prabir Pradhanca7d7232020-01-31 17:42:34 -0800312
313 EXPECT_TRUE(mPolicy->noResourcesAreLoaded());
314
315 ensureDisplayViewportIsSet();
316}
317
Arpit Singh80fd68a2024-03-26 18:41:06 +0000318TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) {
319 ensureDisplayViewportIsSet();
320
321 PointerCoords testSpotCoords;
322 testSpotCoords.clear();
323 testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
324 testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
325 BitSet32 testIdBits;
326 testIdBits.markBit(0);
327 std::array<uint32_t, MAX_POINTER_ID + 1> testIdToIndex;
328
329 sp<MockSprite> testSpotSprite(new NiceMock<MockSprite>);
330
331 // By default sprite is not marked secure
332 EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testSpotSprite));
333 EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
334
335 // Update spots to sync state with sprite
336 mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
337 ADISPLAY_ID_DEFAULT);
338 testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
339
340 // Marking the display to skip screenshot should update sprite as well
341 mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
342 EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
343
344 // Update spots to sync state with sprite
345 mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
346 ADISPLAY_ID_DEFAULT);
347 testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
348
349 // Reset flag and verify again
350 mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
351 EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
352 mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
353 ADISPLAY_ID_DEFAULT);
354 testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
355}
356
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800357class PointerControllerWindowInfoListenerTest : public Test {};
358
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800359TEST_F(PointerControllerWindowInfoListenerTest,
360 doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000361 sp<Looper> looper = new Looper(false);
362 auto spriteController = NiceMock<MockSpriteController>(looper);
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800363 sp<android::gui::WindowInfosListener> registeredListener;
364 sp<android::gui::WindowInfosListener> localListenerCopy;
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700365 sp<MockPointerControllerPolicyInterface> policy = new MockPointerControllerPolicyInterface();
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800366 {
Siarhei Vishniakoua4ea3002023-09-26 18:40:00 -0700367 TestPointerController pointerController(registeredListener, policy, looper,
368 spriteController);
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800369 ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
370 localListenerCopy = registeredListener;
371 }
372 EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
Patrick Williams8e47a672023-05-01 11:30:37 -0500373 localListenerCopy->onWindowInfosChanged({{}, {}, 0, 0});
Prabir Pradhan5693cee2021-12-31 06:51:15 -0800374}
375
Garfield Tanc15eb912019-08-05 16:47:40 -0700376} // namespace android