blob: eecc741a3bbb1a1e9c272b3385e5ab8ed3a0d00c [file] [log] [blame]
Liam Harringtonc782be62020-07-17 19:48:24 +00001/*
2 * Copyright (C) 2020 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#define LOG_TAG "MouseCursorController"
18//#define LOG_NDEBUG 0
19
20// Log debug messages about pointer updates
21#define DEBUG_MOUSE_CURSOR_UPDATES 0
22
23#include "MouseCursorController.h"
24
Brandon Pollack015f5d92022-06-02 06:59:33 +000025#include <input/Input.h>
Liam Harringtonc782be62020-07-17 19:48:24 +000026#include <log/log.h>
27
Liam Harringtonc782be62020-07-17 19:48:24 +000028namespace {
29// Time to spend fading out the pointer completely.
30const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
31} // namespace
32
33namespace android {
34
35// --- MouseCursorController ---
36
37MouseCursorController::MouseCursorController(PointerControllerContext& context)
38 : mContext(context) {
39 std::scoped_lock lock(mLock);
40
Seunghwan Choi670b33d2023-01-13 21:12:59 +090041 mLocked.stylusHoverMode = false;
42
Liam Harringtonc782be62020-07-17 19:48:24 +000043 mLocked.animationFrameIndex = 0;
44 mLocked.lastFrameUpdatedTime = 0;
45
46 mLocked.pointerFadeDirection = 0;
47 mLocked.pointerX = 0;
48 mLocked.pointerY = 0;
49 mLocked.pointerAlpha = 0.0f; // pointer is initially faded
Prabir Pradhan27c6d992023-08-18 19:44:55 +000050 mLocked.pointerSprite = mContext.getSpriteController().createSprite();
Liam Harringtonc782be62020-07-17 19:48:24 +000051 mLocked.updatePointerIcon = false;
Seunghwan Choi670b33d2023-01-13 21:12:59 +090052 mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
53 mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
Liam Harringtonc782be62020-07-17 19:48:24 +000054
55 mLocked.resourcesLoaded = false;
Liam Harringtonc782be62020-07-17 19:48:24 +000056}
57
58MouseCursorController::~MouseCursorController() {
59 std::scoped_lock lock(mLock);
60
61 mLocked.pointerSprite.clear();
62}
63
Prabir Pradhanb5dadec2023-02-28 17:43:09 +000064std::optional<FloatRect> MouseCursorController::getBounds() const {
Liam Harringtonc782be62020-07-17 19:48:24 +000065 std::scoped_lock lock(mLock);
66
Prabir Pradhanb5dadec2023-02-28 17:43:09 +000067 return getBoundsLocked();
Liam Harringtonc782be62020-07-17 19:48:24 +000068}
69
Prabir Pradhanb5dadec2023-02-28 17:43:09 +000070std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
Liam Harringtonc782be62020-07-17 19:48:24 +000071 if (!mLocked.viewport.isValid()) {
Prabir Pradhanb5dadec2023-02-28 17:43:09 +000072 return {};
Liam Harringtonc782be62020-07-17 19:48:24 +000073 }
74
Prabir Pradhanb5dadec2023-02-28 17:43:09 +000075 return FloatRect{
76 static_cast<float>(mLocked.viewport.logicalLeft),
77 static_cast<float>(mLocked.viewport.logicalTop),
78 static_cast<float>(mLocked.viewport.logicalRight - 1),
79 static_cast<float>(mLocked.viewport.logicalBottom - 1),
80 };
Liam Harringtonc782be62020-07-17 19:48:24 +000081}
82
83void MouseCursorController::move(float deltaX, float deltaY) {
84#if DEBUG_MOUSE_CURSOR_UPDATES
85 ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
86#endif
87 if (deltaX == 0.0f && deltaY == 0.0f) {
88 return;
89 }
90
91 std::scoped_lock lock(mLock);
92
93 setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
94}
95
Liam Harringtonc782be62020-07-17 19:48:24 +000096void MouseCursorController::setPosition(float x, float y) {
97#if DEBUG_MOUSE_CURSOR_UPDATES
98 ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
99#endif
100 std::scoped_lock lock(mLock);
101 setPositionLocked(x, y);
102}
103
104void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
Prabir Pradhanb5dadec2023-02-28 17:43:09 +0000105 const auto bounds = getBoundsLocked();
106 if (!bounds) return;
107
108 mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
109 mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
110
111 updatePointerLocked();
Liam Harringtonc782be62020-07-17 19:48:24 +0000112}
113
Prabir Pradhanb5dadec2023-02-28 17:43:09 +0000114FloatPoint MouseCursorController::getPosition() const {
Liam Harringtonc782be62020-07-17 19:48:24 +0000115 std::scoped_lock lock(mLock);
116
Prabir Pradhanb5dadec2023-02-28 17:43:09 +0000117 return {mLocked.pointerX, mLocked.pointerY};
Liam Harringtonc782be62020-07-17 19:48:24 +0000118}
119
Linnan Li0defadf2024-05-05 19:17:05 +0800120ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
Liam Harringtonc782be62020-07-17 19:48:24 +0000121 std::scoped_lock lock(mLock);
122 return mLocked.viewport.displayId;
123}
124
125void MouseCursorController::fade(PointerControllerInterface::Transition transition) {
126 std::scoped_lock lock(mLock);
127
128 // Remove the inactivity timeout, since we are fading now.
129 mContext.removeInactivityTimeout();
130
131 // Start fading.
132 if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
133 mLocked.pointerFadeDirection = 0;
134 mLocked.pointerAlpha = 0.0f;
135 updatePointerLocked();
136 } else {
137 mLocked.pointerFadeDirection = -1;
Liam Harringtonce637132020-08-14 04:00:11 +0000138 startAnimationLocked();
Liam Harringtonc782be62020-07-17 19:48:24 +0000139 }
140}
141
142void MouseCursorController::unfade(PointerControllerInterface::Transition transition) {
143 std::scoped_lock lock(mLock);
144
145 // Always reset the inactivity timer.
146 mContext.resetInactivityTimeout();
147
148 // Start unfading.
149 if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
150 mLocked.pointerFadeDirection = 0;
151 mLocked.pointerAlpha = 1.0f;
152 updatePointerLocked();
153 } else {
154 mLocked.pointerFadeDirection = 1;
Liam Harringtonce637132020-08-14 04:00:11 +0000155 startAnimationLocked();
Liam Harringtonc782be62020-07-17 19:48:24 +0000156 }
157}
158
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900159void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) {
160 std::scoped_lock lock(mLock);
161
162 if (mLocked.stylusHoverMode != stylusHoverMode) {
163 mLocked.stylusHoverMode = stylusHoverMode;
164 mLocked.updatePointerIcon = true;
165 }
166}
167
Arpit Singhf4ae0ac2024-03-26 18:41:06 +0000168void MouseCursorController::setSkipScreenshot(bool skip) {
169 std::scoped_lock lock(mLock);
170 if (mLocked.skipScreenshot == skip) {
171 return;
172 }
173 mLocked.skipScreenshot = skip;
174 updatePointerLocked();
175}
176
Liam Harringtonc782be62020-07-17 19:48:24 +0000177void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
178 std::scoped_lock lock(mLock);
179
180 loadResourcesLocked(getAdditionalMouseResources);
181 updatePointerLocked();
182}
183
184/**
185 * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
186 * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
187 */
188static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
189 width = viewport.deviceWidth;
190 height = viewport.deviceHeight;
191
Michael Wrightecde4d02022-11-25 00:20:19 +0000192 if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
Liam Harringtonc782be62020-07-17 19:48:24 +0000193 std::swap(width, height);
194 }
195}
196
197void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
198 bool getAdditionalMouseResources) {
199 std::scoped_lock lock(mLock);
200
201 if (viewport == mLocked.viewport) {
202 return;
203 }
204
205 const DisplayViewport oldViewport = mLocked.viewport;
206 mLocked.viewport = viewport;
207
208 int32_t oldDisplayWidth, oldDisplayHeight;
209 getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
210 int32_t newDisplayWidth, newDisplayHeight;
211 getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
212
213 // Reset cursor position to center if size or display changed.
214 if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
215 oldDisplayHeight != newDisplayHeight) {
Prabir Pradhanb5dadec2023-02-28 17:43:09 +0000216 if (const auto bounds = getBoundsLocked(); bounds) {
217 mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
218 mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
Liam Harringtonc782be62020-07-17 19:48:24 +0000219 // Reload icon resources for density may be changed.
220 loadResourcesLocked(getAdditionalMouseResources);
221 } else {
222 mLocked.pointerX = 0;
223 mLocked.pointerY = 0;
224 }
225 } else if (oldViewport.orientation != viewport.orientation) {
226 // Apply offsets to convert from the pixel top-left corner position to the pixel center.
227 // This creates an invariant frame of reference that we can easily rotate when
228 // taking into account that the pointer may be located at fractional pixel offsets.
229 float x = mLocked.pointerX + 0.5f;
230 float y = mLocked.pointerY + 0.5f;
231 float temp;
232
233 // Undo the previous rotation.
234 switch (oldViewport.orientation) {
Michael Wrightecde4d02022-11-25 00:20:19 +0000235 case ui::ROTATION_90:
Liam Harringtonc782be62020-07-17 19:48:24 +0000236 temp = x;
237 x = oldViewport.deviceHeight - y;
238 y = temp;
239 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000240 case ui::ROTATION_180:
Liam Harringtonc782be62020-07-17 19:48:24 +0000241 x = oldViewport.deviceWidth - x;
242 y = oldViewport.deviceHeight - y;
243 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000244 case ui::ROTATION_270:
Liam Harringtonc782be62020-07-17 19:48:24 +0000245 temp = x;
246 x = y;
247 y = oldViewport.deviceWidth - temp;
248 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000249 case ui::ROTATION_0:
250 break;
Liam Harringtonc782be62020-07-17 19:48:24 +0000251 }
252
253 // Perform the new rotation.
254 switch (viewport.orientation) {
Michael Wrightecde4d02022-11-25 00:20:19 +0000255 case ui::ROTATION_90:
Liam Harringtonc782be62020-07-17 19:48:24 +0000256 temp = x;
257 x = y;
258 y = viewport.deviceHeight - temp;
259 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000260 case ui::ROTATION_180:
Liam Harringtonc782be62020-07-17 19:48:24 +0000261 x = viewport.deviceWidth - x;
262 y = viewport.deviceHeight - y;
263 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000264 case ui::ROTATION_270:
Liam Harringtonc782be62020-07-17 19:48:24 +0000265 temp = x;
266 x = viewport.deviceWidth - y;
267 y = temp;
268 break;
Michael Wrightecde4d02022-11-25 00:20:19 +0000269 case ui::ROTATION_0:
270 break;
Liam Harringtonc782be62020-07-17 19:48:24 +0000271 }
272
273 // Apply offsets to convert from the pixel center to the pixel top-left corner position
274 // and save the results.
275 mLocked.pointerX = x - 0.5f;
276 mLocked.pointerY = y - 0.5f;
277 }
278
279 updatePointerLocked();
280}
281
Brandon Pollack015f5d92022-06-02 06:59:33 +0000282void MouseCursorController::updatePointerIcon(PointerIconStyle iconId) {
Liam Harringtonc782be62020-07-17 19:48:24 +0000283 std::scoped_lock lock(mLock);
284
285 if (mLocked.requestedPointerType != iconId) {
286 mLocked.requestedPointerType = iconId;
287 mLocked.updatePointerIcon = true;
288 updatePointerLocked();
289 }
290}
291
292void MouseCursorController::setCustomPointerIcon(const SpriteIcon& icon) {
293 std::scoped_lock lock(mLock);
294
Brandon Pollack015f5d92022-06-02 06:59:33 +0000295 const PointerIconStyle iconId = mContext.getPolicy()->getCustomPointerIconId();
Liam Harringtonc782be62020-07-17 19:48:24 +0000296 mLocked.additionalMouseResources[iconId] = icon;
297 mLocked.requestedPointerType = iconId;
298 mLocked.updatePointerIcon = true;
299 updatePointerLocked();
300}
301
Liam Harringtonce637132020-08-14 04:00:11 +0000302bool MouseCursorController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
Liam Harringtonc782be62020-07-17 19:48:24 +0000303 nsecs_t frameDelay = timestamp - mContext.getAnimationTime();
Liam Harringtonce637132020-08-14 04:00:11 +0000304 bool keepAnimating = false;
Liam Harringtonc782be62020-07-17 19:48:24 +0000305
306 // Animate pointer fade.
307 if (mLocked.pointerFadeDirection < 0) {
308 mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
309 if (mLocked.pointerAlpha <= 0.0f) {
310 mLocked.pointerAlpha = 0.0f;
311 mLocked.pointerFadeDirection = 0;
312 } else {
313 keepAnimating = true;
314 }
315 updatePointerLocked();
316 } else if (mLocked.pointerFadeDirection > 0) {
317 mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
318 if (mLocked.pointerAlpha >= 1.0f) {
319 mLocked.pointerAlpha = 1.0f;
320 mLocked.pointerFadeDirection = 0;
321 } else {
322 keepAnimating = true;
323 }
324 updatePointerLocked();
325 }
Liam Harringtonc782be62020-07-17 19:48:24 +0000326 return keepAnimating;
327}
328
Liam Harringtonce637132020-08-14 04:00:11 +0000329bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
Brandon Pollack015f5d92022-06-02 06:59:33 +0000330 std::map<PointerIconStyle, PointerAnimation>::const_iterator iter =
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900331 mLocked.animationResources.find(mLocked.resolvedPointerType);
Liam Harringtonc782be62020-07-17 19:48:24 +0000332 if (iter == mLocked.animationResources.end()) {
333 return false;
334 }
335
336 if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000337 auto& spriteController = mContext.getSpriteController();
338 spriteController.openTransaction();
Liam Harringtonc782be62020-07-17 19:48:24 +0000339
340 int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
341 mLocked.animationFrameIndex += incr;
342 mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
343 while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
344 mLocked.animationFrameIndex -= iter->second.animationFrames.size();
345 }
346 mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
347
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000348 spriteController.closeTransaction();
Liam Harringtonc782be62020-07-17 19:48:24 +0000349 }
Liam Harringtonc782be62020-07-17 19:48:24 +0000350 // Keep animating.
351 return true;
352}
353
354void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
355 if (!mLocked.viewport.isValid()) {
356 return;
357 }
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000358 auto& spriteController = mContext.getSpriteController();
359 spriteController.openTransaction();
Liam Harringtonc782be62020-07-17 19:48:24 +0000360
361 mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
362 mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
363 mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
Arpit Singhf4ae0ac2024-03-26 18:41:06 +0000364 mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot);
Liam Harringtonc782be62020-07-17 19:48:24 +0000365
366 if (mLocked.pointerAlpha > 0) {
367 mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
368 mLocked.pointerSprite->setVisible(true);
369 } else {
370 mLocked.pointerSprite->setVisible(false);
371 }
372
373 if (mLocked.updatePointerIcon) {
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900374 mLocked.resolvedPointerType = mLocked.requestedPointerType;
375 const PointerIconStyle defaultPointerIconId =
376 mContext.getPolicy()->getDefaultPointerIconId();
377 if (mLocked.resolvedPointerType == PointerIconStyle::TYPE_NOT_SPECIFIED) {
378 mLocked.resolvedPointerType = mLocked.stylusHoverMode
379 ? mContext.getPolicy()->getDefaultStylusIconId()
380 : defaultPointerIconId;
381 }
382
383 if (mLocked.resolvedPointerType == defaultPointerIconId) {
Liam Harringtonc782be62020-07-17 19:48:24 +0000384 mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
385 } else {
Brandon Pollack015f5d92022-06-02 06:59:33 +0000386 std::map<PointerIconStyle, SpriteIcon>::const_iterator iter =
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900387 mLocked.additionalMouseResources.find(mLocked.resolvedPointerType);
Liam Harringtonc782be62020-07-17 19:48:24 +0000388 if (iter != mLocked.additionalMouseResources.end()) {
Brandon Pollack015f5d92022-06-02 06:59:33 +0000389 std::map<PointerIconStyle, PointerAnimation>::const_iterator anim_iter =
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900390 mLocked.animationResources.find(mLocked.resolvedPointerType);
Liam Harringtonc782be62020-07-17 19:48:24 +0000391 if (anim_iter != mLocked.animationResources.end()) {
392 mLocked.animationFrameIndex = 0;
393 mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
Liam Harringtonce637132020-08-14 04:00:11 +0000394 startAnimationLocked();
Liam Harringtonc782be62020-07-17 19:48:24 +0000395 }
396 mLocked.pointerSprite->setIcon(iter->second);
397 } else {
Seunghwan Choi670b33d2023-01-13 21:12:59 +0900398 ALOGW("Can't find the resource for icon id %d", mLocked.resolvedPointerType);
Liam Harringtonc782be62020-07-17 19:48:24 +0000399 mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
400 }
401 }
402 mLocked.updatePointerIcon = false;
403 }
404
Prabir Pradhan27c6d992023-08-18 19:44:55 +0000405 spriteController.closeTransaction();
Liam Harringtonc782be62020-07-17 19:48:24 +0000406}
407
408void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
409 if (!mLocked.viewport.isValid()) {
410 return;
411 }
412
413 if (!mLocked.resourcesLoaded) mLocked.resourcesLoaded = true;
414
415 sp<PointerControllerPolicyInterface> policy = mContext.getPolicy();
416 policy->loadPointerResources(&mResources, mLocked.viewport.displayId);
417 policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
418
419 mLocked.additionalMouseResources.clear();
420 mLocked.animationResources.clear();
421 if (getAdditionalMouseResources) {
422 policy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
423 &mLocked.animationResources,
424 mLocked.viewport.displayId);
425 }
426
427 mLocked.updatePointerIcon = true;
428}
429
430bool MouseCursorController::isViewportValid() {
431 std::scoped_lock lock(mLock);
432 return mLocked.viewport.isValid();
433}
434
435void MouseCursorController::getAdditionalMouseResources() {
436 std::scoped_lock lock(mLock);
437
438 if (mLocked.additionalMouseResources.empty()) {
439 mContext.getPolicy()->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
440 &mLocked.animationResources,
441 mLocked.viewport.displayId);
442 }
443 mLocked.updatePointerIcon = true;
444 updatePointerLocked();
445}
446
447bool MouseCursorController::resourcesLoaded() {
448 std::scoped_lock lock(mLock);
449 return mLocked.resourcesLoaded;
450}
451
Liam Harringtonce637132020-08-14 04:00:11 +0000452bool MouseCursorController::doAnimations(nsecs_t timestamp) {
453 std::scoped_lock lock(mLock);
454 bool keepFading = doFadingAnimationLocked(timestamp);
455 bool keepBitmap = doBitmapAnimationLocked(timestamp);
456 bool keepAnimating = keepFading || keepBitmap;
457 if (!keepAnimating) {
458 /*
459 * We know that this callback will be removed before another
460 * is added. mLock in PointerAnimator will not be released
461 * until after this is removed, and adding another callback
462 * requires that lock. Thus it's safe to set mLocked.animating
463 * here.
464 */
465 mLocked.animating = false;
466 }
467 return keepAnimating;
468}
469
470void MouseCursorController::startAnimationLocked() REQUIRES(mLock) {
471 using namespace std::placeholders;
472
473 if (mLocked.animating) {
474 return;
475 }
476 mLocked.animating = true;
477
478 std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
479 /*
Siarhei Vishniakou445af182024-05-13 13:17:05 -0700480 * Using ui::LogicalDisplayId::INVALID for displayId here to avoid removing the callback
Liam Harringtonce637132020-08-14 04:00:11 +0000481 * if a TouchSpotController with the same display is removed.
482 */
Siarhei Vishniakou445af182024-05-13 13:17:05 -0700483 mContext.addAnimationCallback(ui::LogicalDisplayId::INVALID, func);
Liam Harringtonce637132020-08-14 04:00:11 +0000484}
485
Liam Harringtonc782be62020-07-17 19:48:24 +0000486} // namespace android