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 // clang-format off
18 #include "../Macros.h"
19 // clang-format on
20
21 #include "CursorInputMapper.h"
22
23 #include "CursorButtonAccumulator.h"
24 #include "CursorScrollAccumulator.h"
25 #include "PointerControllerInterface.h"
26 #include "TouchCursorInputMapperCommon.h"
27
28 namespace android {
29
30 // --- CursorMotionAccumulator ---
31
CursorMotionAccumulator()32 CursorMotionAccumulator::CursorMotionAccumulator() {
33 clearRelativeAxes();
34 }
35
reset(InputDeviceContext & deviceContext)36 void CursorMotionAccumulator::reset(InputDeviceContext& deviceContext) {
37 clearRelativeAxes();
38 }
39
clearRelativeAxes()40 void CursorMotionAccumulator::clearRelativeAxes() {
41 mRelX = 0;
42 mRelY = 0;
43 }
44
process(const RawEvent * rawEvent)45 void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
46 if (rawEvent->type == EV_REL) {
47 switch (rawEvent->code) {
48 case REL_X:
49 mRelX = rawEvent->value;
50 break;
51 case REL_Y:
52 mRelY = rawEvent->value;
53 break;
54 }
55 }
56 }
57
finishSync()58 void CursorMotionAccumulator::finishSync() {
59 clearRelativeAxes();
60 }
61
62 // --- CursorInputMapper ---
63
CursorInputMapper(InputDeviceContext & deviceContext)64 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext)
65 : InputMapper(deviceContext) {}
66
~CursorInputMapper()67 CursorInputMapper::~CursorInputMapper() {}
68
getSources()69 uint32_t CursorInputMapper::getSources() {
70 return mSource;
71 }
72
populateDeviceInfo(InputDeviceInfo * info)73 void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
74 InputMapper::populateDeviceInfo(info);
75
76 if (mParameters.mode == Parameters::MODE_POINTER) {
77 float minX, minY, maxX, maxY;
78 if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
79 info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
80 info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
81 }
82 } else {
83 info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
84 info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
85 info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale,
86 0.0f);
87 info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale,
88 0.0f);
89 }
90 info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
91
92 if (mCursorScrollAccumulator.haveRelativeVWheel()) {
93 info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
94 }
95 if (mCursorScrollAccumulator.haveRelativeHWheel()) {
96 info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
97 }
98 }
99
dump(std::string & dump)100 void CursorInputMapper::dump(std::string& dump) {
101 dump += INDENT2 "Cursor Input Mapper:\n";
102 dumpParameters(dump);
103 dump += StringPrintf(INDENT3 "XScale: %0.3f\n", mXScale);
104 dump += StringPrintf(INDENT3 "YScale: %0.3f\n", mYScale);
105 dump += StringPrintf(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
106 dump += StringPrintf(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
107 dump += StringPrintf(INDENT3 "HaveVWheel: %s\n",
108 toString(mCursorScrollAccumulator.haveRelativeVWheel()));
109 dump += StringPrintf(INDENT3 "HaveHWheel: %s\n",
110 toString(mCursorScrollAccumulator.haveRelativeHWheel()));
111 dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
112 dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
113 dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation);
114 dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
115 dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
116 dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime);
117 }
118
configure(nsecs_t when,const InputReaderConfiguration * config,uint32_t changes)119 void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
120 uint32_t changes) {
121 InputMapper::configure(when, config, changes);
122
123 if (!changes) { // first time only
124 mCursorScrollAccumulator.configure(getDeviceContext());
125
126 // Configure basic parameters.
127 configureParameters();
128
129 // Configure device mode.
130 switch (mParameters.mode) {
131 case Parameters::MODE_POINTER_RELATIVE:
132 // Should not happen during first time configuration.
133 ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER");
134 mParameters.mode = Parameters::MODE_POINTER;
135 [[fallthrough]];
136 case Parameters::MODE_POINTER:
137 mSource = AINPUT_SOURCE_MOUSE;
138 mXPrecision = 1.0f;
139 mYPrecision = 1.0f;
140 mXScale = 1.0f;
141 mYScale = 1.0f;
142 mPointerController = getContext()->getPointerController(getDeviceId());
143 break;
144 case Parameters::MODE_NAVIGATION:
145 mSource = AINPUT_SOURCE_TRACKBALL;
146 mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
147 mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
148 mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
149 mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
150 break;
151 }
152
153 mVWheelScale = 1.0f;
154 mHWheelScale = 1.0f;
155 }
156
157 if ((!changes && config->pointerCapture) ||
158 (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)) {
159 if (config->pointerCapture) {
160 if (mParameters.mode == Parameters::MODE_POINTER) {
161 mParameters.mode = Parameters::MODE_POINTER_RELATIVE;
162 mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
163 // Keep PointerController around in order to preserve the pointer position.
164 mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
165 } else {
166 ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
167 }
168 } else {
169 if (mParameters.mode == Parameters::MODE_POINTER_RELATIVE) {
170 mParameters.mode = Parameters::MODE_POINTER;
171 mSource = AINPUT_SOURCE_MOUSE;
172 } else {
173 ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE");
174 }
175 }
176 bumpGeneration();
177 if (changes) {
178 NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
179 getListener()->notifyDeviceReset(&args);
180 }
181 }
182
183 if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
184 mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
185 mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
186 mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
187 }
188
189 if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
190 mOrientation = DISPLAY_ORIENTATION_0;
191 mDisplayWidth = 0;
192 mDisplayHeight = 0;
193 const bool isOrientedDevice =
194 (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
195
196 if (isPerWindowInputRotationEnabled()) {
197 // When per-window input rotation is enabled, InputReader works in the un-rotated
198 // coordinate space, so we don't need to do anything if the device is already
199 // orientation-aware. If the device is not orientation-aware, then we need to apply the
200 // inverse rotation of the display so that when the display rotation is applied later
201 // as a part of the per-window transform, we get the expected screen coordinates.
202 if (!isOrientedDevice) {
203 std::optional<DisplayViewport> internalViewport =
204 config->getDisplayViewportByType(ViewportType::INTERNAL);
205 if (internalViewport) {
206 mOrientation = getInverseRotation(internalViewport->orientation);
207 mDisplayWidth = internalViewport->deviceWidth;
208 mDisplayHeight = internalViewport->deviceHeight;
209 }
210 }
211 } else {
212 if (isOrientedDevice) {
213 std::optional<DisplayViewport> internalViewport =
214 config->getDisplayViewportByType(ViewportType::INTERNAL);
215 if (internalViewport) {
216 mOrientation = internalViewport->orientation;
217 }
218 }
219 }
220
221 bumpGeneration();
222 }
223 }
224
configureParameters()225 void CursorInputMapper::configureParameters() {
226 mParameters.mode = Parameters::MODE_POINTER;
227 String8 cursorModeString;
228 if (getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.mode"),
229 cursorModeString)) {
230 if (cursorModeString == "navigation") {
231 mParameters.mode = Parameters::MODE_NAVIGATION;
232 } else if (cursorModeString != "pointer" && cursorModeString != "default") {
233 ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
234 }
235 }
236
237 mParameters.orientationAware = false;
238 getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
239 mParameters.orientationAware);
240
241 mParameters.hasAssociatedDisplay = false;
242 if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
243 mParameters.hasAssociatedDisplay = true;
244 }
245 }
246
dumpParameters(std::string & dump)247 void CursorInputMapper::dumpParameters(std::string& dump) {
248 dump += INDENT3 "Parameters:\n";
249 dump += StringPrintf(INDENT4 "HasAssociatedDisplay: %s\n",
250 toString(mParameters.hasAssociatedDisplay));
251
252 switch (mParameters.mode) {
253 case Parameters::MODE_POINTER:
254 dump += INDENT4 "Mode: pointer\n";
255 break;
256 case Parameters::MODE_POINTER_RELATIVE:
257 dump += INDENT4 "Mode: relative pointer\n";
258 break;
259 case Parameters::MODE_NAVIGATION:
260 dump += INDENT4 "Mode: navigation\n";
261 break;
262 default:
263 ALOG_ASSERT(false);
264 }
265
266 dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
267 }
268
reset(nsecs_t when)269 void CursorInputMapper::reset(nsecs_t when) {
270 mButtonState = 0;
271 mDownTime = 0;
272
273 mPointerVelocityControl.reset();
274 mWheelXVelocityControl.reset();
275 mWheelYVelocityControl.reset();
276
277 mCursorButtonAccumulator.reset(getDeviceContext());
278 mCursorMotionAccumulator.reset(getDeviceContext());
279 mCursorScrollAccumulator.reset(getDeviceContext());
280
281 InputMapper::reset(when);
282 }
283
process(const RawEvent * rawEvent)284 void CursorInputMapper::process(const RawEvent* rawEvent) {
285 mCursorButtonAccumulator.process(rawEvent);
286 mCursorMotionAccumulator.process(rawEvent);
287 mCursorScrollAccumulator.process(rawEvent);
288
289 if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
290 sync(rawEvent->when, rawEvent->readTime);
291 }
292 }
293
sync(nsecs_t when,nsecs_t readTime)294 void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) {
295 int32_t lastButtonState = mButtonState;
296 int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
297 mButtonState = currentButtonState;
298
299 bool wasDown = isPointerDown(lastButtonState);
300 bool down = isPointerDown(currentButtonState);
301 bool downChanged;
302 if (!wasDown && down) {
303 mDownTime = when;
304 downChanged = true;
305 } else if (wasDown && !down) {
306 downChanged = true;
307 } else {
308 downChanged = false;
309 }
310 nsecs_t downTime = mDownTime;
311 bool buttonsChanged = currentButtonState != lastButtonState;
312 int32_t buttonsPressed = currentButtonState & ~lastButtonState;
313 int32_t buttonsReleased = lastButtonState & ~currentButtonState;
314
315 float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
316 float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
317 bool moved = deltaX != 0 || deltaY != 0;
318
319 // Rotate delta according to orientation.
320 rotateDelta(mOrientation, &deltaX, &deltaY);
321
322 // Move the pointer.
323 PointerProperties pointerProperties;
324 pointerProperties.clear();
325 pointerProperties.id = 0;
326 pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
327
328 PointerCoords pointerCoords;
329 pointerCoords.clear();
330
331 float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
332 float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
333 bool scrolled = vscroll != 0 || hscroll != 0;
334
335 mWheelYVelocityControl.move(when, nullptr, &vscroll);
336 mWheelXVelocityControl.move(when, &hscroll, nullptr);
337
338 mPointerVelocityControl.move(when, &deltaX, &deltaY);
339
340 int32_t displayId = ADISPLAY_ID_NONE;
341 float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
342 float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
343 if (mSource == AINPUT_SOURCE_MOUSE) {
344 if (moved || scrolled || buttonsChanged) {
345 mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
346
347 if (moved) {
348 float dx = deltaX;
349 float dy = deltaY;
350 if (isPerWindowInputRotationEnabled()) {
351 // Rotate the delta from InputReader's un-rotated coordinate space to
352 // PointerController's rotated coordinate space that is oriented with the
353 // viewport.
354 rotateDelta(getInverseRotation(mOrientation), &dx, &dy);
355 }
356 mPointerController->move(dx, dy);
357 }
358
359 if (buttonsChanged) {
360 mPointerController->setButtonState(currentButtonState);
361 }
362
363 mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
364 }
365
366 mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
367 if (isPerWindowInputRotationEnabled()) {
368 // Rotate the cursor position that is in PointerController's rotated coordinate space
369 // to InputReader's un-rotated coordinate space.
370 rotatePoint(mOrientation, xCursorPosition /*byRef*/, yCursorPosition /*byRef*/,
371 mDisplayWidth, mDisplayHeight);
372 }
373 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
374 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
375 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
376 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
377 displayId = mPointerController->getDisplayId();
378 } else {
379 // Pointer capture and navigation modes
380 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
381 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
382 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
383 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
384 }
385
386 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
387
388 // Moving an external trackball or mouse should wake the device.
389 // We don't do this for internal cursor devices to prevent them from waking up
390 // the device in your pocket.
391 // TODO: Use the input device configuration to control this behavior more finely.
392 uint32_t policyFlags = 0;
393 if ((buttonsPressed || moved || scrolled) && getDeviceContext().isExternal()) {
394 policyFlags |= POLICY_FLAG_WAKE;
395 }
396
397 // Synthesize key down from buttons if needed.
398 synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(),
399 mSource, displayId, policyFlags, lastButtonState, currentButtonState);
400
401 // Send motion event.
402 if (downChanged || moved || scrolled || buttonsChanged) {
403 int32_t metaState = getContext()->getGlobalMetaState();
404 int32_t buttonState = lastButtonState;
405 int32_t motionEventAction;
406 if (downChanged) {
407 motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
408 } else if (down || (mSource != AINPUT_SOURCE_MOUSE)) {
409 motionEventAction = AMOTION_EVENT_ACTION_MOVE;
410 } else {
411 motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
412 }
413
414 if (buttonsReleased) {
415 BitSet32 released(buttonsReleased);
416 while (!released.isEmpty()) {
417 int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
418 buttonState &= ~actionButton;
419 NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime,
420 getDeviceId(), mSource, displayId, policyFlags,
421 AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
422 metaState, buttonState, MotionClassification::NONE,
423 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
424 &pointerCoords, mXPrecision, mYPrecision,
425 xCursorPosition, yCursorPosition, downTime,
426 /* videoFrames */ {});
427 getListener()->notifyMotion(&releaseArgs);
428 }
429 }
430
431 NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
432 displayId, policyFlags, motionEventAction, 0, 0, metaState,
433 currentButtonState, MotionClassification::NONE,
434 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
435 mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime,
436 /* videoFrames */ {});
437 getListener()->notifyMotion(&args);
438
439 if (buttonsPressed) {
440 BitSet32 pressed(buttonsPressed);
441 while (!pressed.isEmpty()) {
442 int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
443 buttonState |= actionButton;
444 NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
445 mSource, displayId, policyFlags,
446 AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
447 metaState, buttonState, MotionClassification::NONE,
448 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
449 &pointerCoords, mXPrecision, mYPrecision,
450 xCursorPosition, yCursorPosition, downTime,
451 /* videoFrames */ {});
452 getListener()->notifyMotion(&pressArgs);
453 }
454 }
455
456 ALOG_ASSERT(buttonState == currentButtonState);
457
458 // Send hover move after UP to tell the application that the mouse is hovering now.
459 if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
460 NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
461 mSource, displayId, policyFlags,
462 AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
463 currentButtonState, MotionClassification::NONE,
464 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
465 &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
466 yCursorPosition, downTime, /* videoFrames */ {});
467 getListener()->notifyMotion(&hoverArgs);
468 }
469
470 // Send scroll events.
471 if (scrolled) {
472 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
473 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
474
475 NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
476 mSource, displayId, policyFlags,
477 AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
478 currentButtonState, MotionClassification::NONE,
479 AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
480 &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
481 yCursorPosition, downTime, /* videoFrames */ {});
482 getListener()->notifyMotion(&scrollArgs);
483 }
484 }
485
486 // Synthesize key up from buttons if needed.
487 synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource,
488 displayId, policyFlags, lastButtonState, currentButtonState);
489
490 mCursorMotionAccumulator.finishSync();
491 mCursorScrollAccumulator.finishSync();
492 }
493
getScanCodeState(uint32_t sourceMask,int32_t scanCode)494 int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
495 if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
496 return getDeviceContext().getScanCodeState(scanCode);
497 } else {
498 return AKEY_STATE_UNKNOWN;
499 }
500 }
501
getAssociatedDisplayId()502 std::optional<int32_t> CursorInputMapper::getAssociatedDisplayId() {
503 if (mParameters.hasAssociatedDisplay) {
504 if (mParameters.mode == Parameters::MODE_POINTER) {
505 return std::make_optional(mPointerController->getDisplayId());
506 } else {
507 // If the device is orientationAware and not a mouse,
508 // it expects to dispatch events to any display
509 return std::make_optional(ADISPLAY_ID_NONE);
510 }
511 }
512 return std::nullopt;
513 }
514
515 } // namespace android
516