blob: b69e16ac85b50188f579e9c27b0e2a8202bce50d [file] [log] [blame]
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -07001/*
2 * Copyright (C) 2022 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 "UnwantedInteractionBlocker"
18#include "UnwantedInteractionBlocker.h"
19
20#include <android-base/stringprintf.h>
21#include <inttypes.h>
22#include <linux/input-event-codes.h>
23#include <linux/input.h>
24#include <server_configurable_flags/get_flags.h>
25
26#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
27#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
28
29using android::base::StringPrintf;
30
31namespace android {
32
33// Category (=namespace) name for the input settings that are applied at boot time
34static const char* INPUT_NATIVE_BOOT = "input_native_boot";
35/**
36 * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
37 * 'true' (not case sensitive) or '1'. To disable, specify any other value.
38 */
39static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
40
41static std::string toLower(std::string s) {
42 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
43 return s;
44}
45
46static bool isFromTouchscreen(int32_t source) {
Siarhei Vishniakouf6db4c32022-02-10 19:46:34 -080047 return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN) &&
48 !isFromSource(source, AINPUT_SOURCE_STYLUS);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070049}
50
51static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
52 return ::base::TimeTicks::UnixEpoch() +
53 ::base::Milliseconds(static_cast<float>(ns2ms(eventTime)));
54}
55
56/**
57 * Return true if palm rejection is enabled via the server configurable flags. Return false
58 * otherwise.
59 */
60static bool isPalmRejectionEnabled() {
61 std::string value = toLower(
62 server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
63 PALM_REJECTION_ENABLED, "false"));
64 if (value == "true" || value == "1") {
65 return true;
66 }
67 return false;
68}
69
70static int getLinuxToolType(int32_t toolType) {
71 switch (toolType) {
72 case AMOTION_EVENT_TOOL_TYPE_FINGER:
73 return MT_TOOL_FINGER;
74 case AMOTION_EVENT_TOOL_TYPE_STYLUS:
75 return MT_TOOL_PEN;
76 case AMOTION_EVENT_TOOL_TYPE_PALM:
77 return MT_TOOL_PALM;
78 }
79 ALOGW("Got tool type %" PRId32 ", converting to MT_TOOL_FINGER", toolType);
80 return MT_TOOL_FINGER;
81}
82
83static std::string addPrefix(std::string str, const std::string& prefix) {
84 std::stringstream ss;
85 bool newLineStarted = true;
86 for (const auto& ch : str) {
87 if (newLineStarted) {
88 ss << prefix;
89 newLineStarted = false;
90 }
91 if (ch == '\n') {
92 newLineStarted = true;
93 }
94 ss << ch;
95 }
96 return ss.str();
97}
98
99template <typename T>
100static std::string dumpSet(const std::set<T>& v) {
101 static_assert(std::is_integral<T>::value, "Only integral types can be printed.");
102 std::string out;
103 for (const T& entry : v) {
104 out += out.empty() ? "{" : ", ";
105 out += android::base::StringPrintf("%i", entry);
106 }
107 return out.empty() ? "{}" : (out + "}");
108}
109
110template <typename K, typename V>
111static std::string dumpMap(const std::map<K, V>& map) {
112 static_assert(std::is_integral<K>::value, "Keys should have integral type to be printed.");
113 static_assert(std::is_integral<V>::value, "Values should have integral type to be printed.");
114 std::string out;
115 for (const auto& [k, v] : map) {
116 if (!out.empty()) {
117 out += "\n";
118 }
119 out += android::base::StringPrintf("%i : %i", static_cast<int>(k), static_cast<int>(v));
120 }
121 return out;
122}
123
124static std::string dumpDeviceInfo(const AndroidPalmFilterDeviceInfo& info) {
125 std::string out;
126 out += StringPrintf("max_x = %.2f\n", info.max_x);
127 out += StringPrintf("max_y = %.2f\n", info.max_y);
128 out += StringPrintf("x_res = %.2f\n", info.x_res);
129 out += StringPrintf("y_res = %.2f\n", info.y_res);
130 out += StringPrintf("major_radius_res = %.2f\n", info.major_radius_res);
131 out += StringPrintf("minor_radius_res = %.2f\n", info.minor_radius_res);
132 out += StringPrintf("minor_radius_supported = %s\n",
133 info.minor_radius_supported ? "true" : "false");
134 out += StringPrintf("touch_major_res = %" PRId32 "\n", info.touch_major_res);
135 out += StringPrintf("touch_minor_res = %" PRId32 "\n", info.touch_minor_res);
136 return out;
137}
138
139static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
140 for (size_t i = 0; i < args.pointerCount; i++) {
141 if (pointerId == args.pointerProperties[i].id) {
142 return AMOTION_EVENT_ACTION_POINTER_UP |
143 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
144 }
145 }
146 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
147}
148
149/**
150 * Find the action for individual pointer at the given pointer index.
151 * This is always equal to MotionEvent::getActionMasked, except for
152 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
153 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
154 */
155static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
156 const int32_t actionMasked = MotionEvent::getActionMasked(action);
157 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
158 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
159 return actionMasked;
160 }
161 // This is a POINTER_DOWN or POINTER_UP event
162 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
163 if (pointerIndex == actionIndex) {
164 return actionMasked;
165 }
166 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
167 // pointers
168 return AMOTION_EVENT_ACTION_MOVE;
169}
170
171static const char* toString(bool value) {
172 return value ? "true" : "false";
173}
174
175std::string toString(const ::ui::InProgressTouchEvdev& touch) {
176 return StringPrintf("x=%.1f, y=%.1f, tracking_id=%i, slot=%zu,"
177 " pressure=%.1f, major=%i, minor=%i, "
178 "tool_type=%i, altered=%s, was_touching=%s, touching=%s",
179 touch.x, touch.y, touch.tracking_id, touch.slot, touch.pressure,
180 touch.major, touch.minor, touch.tool_type, toString(touch.altered),
181 toString(touch.was_touching), toString(touch.touching));
182}
183
184/**
185 * Remove the data for the provided pointers from the args. The pointers are identified by their
186 * pointerId, not by the index inside the array.
187 * Return the new NotifyMotionArgs struct that has the remaining pointers.
188 * The only fields that may be different in the returned args from the provided args are:
189 * - action
190 * - pointerCount
191 * - pointerProperties
192 * - pointerCoords
193 * Action might change because it contains a pointer index. If another pointer is removed, the
194 * active pointer index would be shifted.
195 * Do not call this function for events with POINTER_UP or POINTER_DOWN events when removed pointer
196 * id is the acting pointer id.
197 *
198 * @param args the args from which the pointers should be removed
199 * @param pointerIds the pointer ids of the pointers that should be removed
200 */
201NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
202 const std::set<int32_t>& pointerIds) {
203 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
204 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
205 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
206 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
207
208 NotifyMotionArgs newArgs{args};
209 newArgs.pointerCount = 0;
210 int32_t newActionIndex = 0;
211 for (uint32_t i = 0; i < args.pointerCount; i++) {
212 const int32_t pointerId = args.pointerProperties[i].id;
213 if (pointerIds.find(pointerId) != pointerIds.end()) {
214 // skip this pointer
215 if (isPointerUpOrDownAction && i == actionIndex) {
216 // The active pointer is being removed, so the action is no longer valid.
217 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
218 // action later to a proper value.
219 newArgs.action = ACTION_UNKNOWN;
220 }
221 continue;
222 }
223 newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
224 newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
225 if (i == actionIndex) {
226 newActionIndex = newArgs.pointerCount;
227 }
228 newArgs.pointerCount++;
229 }
230 // Update POINTER_DOWN or POINTER_UP actions
231 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
232 newArgs.action =
233 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
234 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
235 if (newArgs.pointerCount == 1) {
236 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
237 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
238 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
239 newArgs.action = AMOTION_EVENT_ACTION_UP;
240 }
241 }
242 }
243 return newArgs;
244}
245
246std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
247 const InputDeviceInfo& deviceInfo) {
248 if (!isFromTouchscreen(deviceInfo.getSources())) {
249 return std::nullopt;
250 }
251 AndroidPalmFilterDeviceInfo out;
252 const InputDeviceInfo::MotionRange* axisX =
253 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
254 if (axisX != nullptr) {
255 out.max_x = axisX->max;
256 out.x_res = axisX->resolution;
257 } else {
258 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
259 deviceInfo.getDisplayName().c_str());
260 return std::nullopt;
261 }
262 const InputDeviceInfo::MotionRange* axisY =
263 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
264 if (axisY != nullptr) {
265 out.max_y = axisY->max;
266 out.y_res = axisY->resolution;
267 } else {
268 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
269 deviceInfo.getDisplayName().c_str());
270 return std::nullopt;
271 }
272 const InputDeviceInfo::MotionRange* axisMajor =
273 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
274 if (axisMajor != nullptr) {
275 out.major_radius_res = axisMajor->resolution;
276 out.touch_major_res = axisMajor->resolution;
277 } else {
278 return std::nullopt;
279 }
280 const InputDeviceInfo::MotionRange* axisMinor =
281 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
282 if (axisMinor != nullptr) {
283 out.minor_radius_res = axisMinor->resolution;
284 out.touch_minor_res = axisMinor->resolution;
285 out.minor_radius_supported = true;
286 } else {
287 out.minor_radius_supported = false;
288 }
289
290 return out;
291}
292
293/**
294 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
295 * that have already been canceled.
296 * The flow of the function is as follows:
297 * 1. Remove all already canceled pointers
298 * 2. Cancel all newly suppressed pointers
299 * 3. Decide what to do with the current event : keep it, or drop it
300 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
301 */
302std::vector<NotifyMotionArgs> cancelSuppressedPointers(
303 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
304 const std::set<int32_t>& newSuppressedPointerIds) {
305 LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
306
307 // First, let's remove the old suppressed pointers. They've already been canceled previously.
308 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
309
310 // Cancel any newly suppressed pointers.
311 std::vector<NotifyMotionArgs> out;
312 const int32_t activePointerId =
313 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
314 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
315 // We will iteratively remove pointers from 'removedArgs'.
316 NotifyMotionArgs removedArgs{oldArgs};
317 for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
318 const int32_t pointerId = oldArgs.pointerProperties[i].id;
319 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
320 // This is a pointer that should not be canceled. Move on.
321 continue;
322 }
323 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
324 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
325 removedArgs = removePointerIds(removedArgs, {pointerId});
326 continue;
327 }
328
329 if (removedArgs.pointerCount == 1) {
330 // We are about to remove the last pointer, which means there will be no more gesture
331 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
332 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
333 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
334 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
335 return {oldArgs};
336 }
337 // Cancel the current pointer
338 out.push_back(removedArgs);
339 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
340 out.back().action = getActionUpForPointerId(out.back(), pointerId);
341
342 // Remove the newly canceled pointer from the args
343 removedArgs = removePointerIds(removedArgs, {pointerId});
344 }
345
346 // Now 'removedArgs' contains only pointers that are valid.
347 if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
348 return out;
349 }
350 out.push_back(removedArgs);
351 return out;
352}
353
354UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
355 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
356
357UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
358 bool enablePalmRejection)
359 : mListener(listener), mEnablePalmRejection(enablePalmRejection) {}
360
361void UnwantedInteractionBlocker::notifyConfigurationChanged(
362 const NotifyConfigurationChangedArgs* args) {
363 mListener.notifyConfigurationChanged(args);
364}
365
366void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
367 mListener.notifyKey(args);
368}
369
370void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800371 const std::vector<NotifyMotionArgs> processedArgs =
372 mPreferStylusOverTouchBlocker.processMotion(*args);
373 for (const NotifyMotionArgs& loopArgs : processedArgs) {
374 notifyMotionInner(&loopArgs);
375 }
376}
377
378void UnwantedInteractionBlocker::notifyMotionInner(const NotifyMotionArgs* args) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700379 auto it = mPalmRejectors.find(args->deviceId);
380 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
381 if (!sendToPalmRejector) {
382 mListener.notifyMotion(args);
383 return;
384 }
385
386 const std::vector<NotifyMotionArgs> newMotions = it->second.processMotion(*args);
387 for (const NotifyMotionArgs& newArgs : newMotions) {
388 mListener.notifyMotion(&newArgs);
389 }
390}
391
392void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
393 mListener.notifySwitch(args);
394}
395
396void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
397 mListener.notifySensor(args);
398}
399
400void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
401 mListener.notifyVibratorState(args);
402}
403void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
404 auto it = mPalmRejectors.find(args->deviceId);
405 if (it != mPalmRejectors.end()) {
406 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
407 // Re-create the object instead of resetting it
408 mPalmRejectors.erase(it);
409 mPalmRejectors.emplace(args->deviceId, info);
410 }
411 mListener.notifyDeviceReset(args);
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800412 mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700413}
414
415void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
416 const NotifyPointerCaptureChangedArgs* args) {
417 mListener.notifyPointerCaptureChanged(args);
418}
419
420void UnwantedInteractionBlocker::notifyInputDevicesChanged(
421 const std::vector<InputDeviceInfo>& inputDevices) {
422 if (!mEnablePalmRejection) {
423 // Palm rejection is disabled. Don't create any palm rejector objects.
424 return;
425 }
426
427 // Let's see which of the existing devices didn't change, so that we can keep them
428 // and prevent event stream disruption
429 std::set<int32_t /*deviceId*/> devicesToKeep;
430 for (const InputDeviceInfo& device : inputDevices) {
431 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
432 if (!info) {
433 continue;
434 }
435
436 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
437 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
438 // Re-create the PalmRejector because the device info has changed.
439 mPalmRejectors.erase(it);
440 mPalmRejectors.emplace(device.getId(), *info);
441 }
442 devicesToKeep.insert(device.getId());
443 }
444 // Delete all devices that we don't need to keep
445 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
446 auto const& [deviceId, _] = item;
447 return devicesToKeep.find(deviceId) == devicesToKeep.end();
448 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800449 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700450}
451
452void UnwantedInteractionBlocker::dump(std::string& dump) {
453 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800454 dump += " mPreferStylusOverTouchBlocker:\n";
455 dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700456 dump += StringPrintf(" mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
457 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
458 toString(isPalmRejectionEnabled()));
459 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
460 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
461 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
462 dump += addPrefix(palmRejector.dump(), " ");
463 }
464}
465
466void UnwantedInteractionBlocker::monitor() {}
467
468UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
469
470void SlotState::update(const NotifyMotionArgs& args) {
471 for (size_t i = 0; i < args.pointerCount; i++) {
472 const int32_t pointerId = args.pointerProperties[i].id;
473 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
474 processPointerId(pointerId, resolvedAction);
475 }
476}
477
478size_t SlotState::findUnusedSlot() const {
479 size_t unusedSlot = 0;
480 // Since the collection is ordered, we can rely on the in-order traversal
481 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
482 if (unusedSlot != slot) {
483 break;
484 }
485 unusedSlot++;
486 }
487 return unusedSlot;
488}
489
490void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
491 switch (MotionEvent::getActionMasked(actionMasked)) {
492 case AMOTION_EVENT_ACTION_DOWN:
493 case AMOTION_EVENT_ACTION_POINTER_DOWN:
494 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
495 // New pointer going down
496 size_t newSlot = findUnusedSlot();
497 mPointerIdsBySlot[newSlot] = pointerId;
498 mSlotsByPointerId[pointerId] = newSlot;
499 return;
500 }
501 case AMOTION_EVENT_ACTION_MOVE:
502 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
503 return;
504 }
505 case AMOTION_EVENT_ACTION_CANCEL:
506 case AMOTION_EVENT_ACTION_POINTER_UP:
507 case AMOTION_EVENT_ACTION_UP:
508 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
509 auto it = mSlotsByPointerId.find(pointerId);
510 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
511 size_t slot = it->second;
512 // Erase this pointer from both collections
513 mPointerIdsBySlot.erase(slot);
514 mSlotsByPointerId.erase(pointerId);
515 return;
516 }
517 }
518 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
519 return;
520}
521
522std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
523 auto it = mSlotsByPointerId.find(pointerId);
524 if (it == mSlotsByPointerId.end()) {
525 return std::nullopt;
526 }
527 return it->second;
528}
529
530std::string SlotState::dump() const {
531 std::string out = "mSlotsByPointerId:\n";
532 out += addPrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
533 out += "mPointerIdsBySlot:\n";
534 out += addPrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
535 return out;
536}
537
538PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
539 std::unique_ptr<::ui::PalmDetectionFilter> filter)
540 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
541 mDeviceInfo(info),
542 mPalmDetectionFilter(std::move(filter)) {
543 if (mPalmDetectionFilter != nullptr) {
544 // This path is used for testing. Non-testing invocations should let this constructor
545 // create a real PalmDetectionFilter
546 return;
547 }
548 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
549 std::make_unique<::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel>(
550 std::vector<float>());
551 mPalmDetectionFilter =
552 std::make_unique<::ui::NeuralStylusPalmDetectionFilter>(mDeviceInfo, std::move(model),
553 mSharedPalmState.get());
554}
555
556std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
557 const AndroidPalmFilterDeviceInfo& deviceInfo,
558 const SlotState& oldSlotState,
559 const SlotState& newSlotState) {
560 std::vector<::ui::InProgressTouchEvdev> touches;
561
562 for (size_t i = 0; i < args.pointerCount; i++) {
563 const int32_t pointerId = args.pointerProperties[i].id;
564 touches.emplace_back(::ui::InProgressTouchEvdev());
565 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
566 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
567 touches.back().tool_type = getLinuxToolType(args.pointerProperties[i].toolType);
568
569 // Whether there is new information for the touch.
570 touches.back().altered = true;
571
572 // Whether the touch was cancelled. Touch events should be ignored till a
573 // new touch is initiated.
574 touches.back().was_cancelled = false;
575
576 // Whether the touch is going to be canceled.
577 touches.back().cancelled = false;
578
579 // Whether the touch is delayed at first appearance. Will not be reported yet.
580 touches.back().delayed = false;
581
582 // Whether the touch was delayed before.
583 touches.back().was_delayed = false;
584
585 // Whether the touch is held until end or no longer held.
586 touches.back().held = false;
587
588 // Whether this touch was held before being sent.
589 touches.back().was_held = false;
590
591 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
592 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
593 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
594 touches.back().was_touching = !isDown;
595
596 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
597 resolvedAction == AMOTION_EVENT_ACTION_UP ||
598 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
599
600 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
601 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
602
603 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
604 if (!slot) {
605 slot = oldSlotState.getSlotForPointerId(pointerId);
606 }
607 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
608 touches.back().slot = *slot;
609 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
610 touches.back().touching = !isUpOrCancel;
611
612 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
613 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
614 touches.back().tool_code = BTN_TOOL_FINGER;
615 // The field 'orientation' is not used for palm rejection
616 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
617 touches.back().reported_tool_type = ::ui::EventPointerType::kTouch;
618 touches.back().stylus_button = false;
619 }
620 return touches;
621}
622
623std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
624 if (mPalmDetectionFilter == nullptr) {
625 return {args};
626 }
627 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
628 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
629 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
630 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
631 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
632 args.action == AMOTION_EVENT_ACTION_SCROLL;
633 if (skipThisEvent) {
634 // Lets not process hover events, button events, or scroll for now.
635 return {args};
636 }
637 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
638 mSuppressedPointerIds.clear();
639 }
640 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
641 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
642
643 // Store the slot state before we call getTouches and update it. This way, we can find
644 // the slots that have been removed due to the incoming event.
645 SlotState oldSlotState = mSlotState;
646 mSlotState.update(args);
647 std::vector<::ui::InProgressTouchEvdev> touches =
648 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
649 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
650
651 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
652
653 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
654 std::set<int32_t> oldSuppressedIds;
655 std::swap(oldSuppressedIds, mSuppressedPointerIds);
656 for (size_t i = 0; i < args.pointerCount; i++) {
657 const int32_t pointerId = args.pointerProperties[i].id;
658 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
659 if (!slot) {
660 slot = mSlotState.getSlotForPointerId(pointerId);
661 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
662 }
663 if (slotsToSuppress.test(*slot)) {
664 mSuppressedPointerIds.insert(pointerId);
665 }
666 }
667
668 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
669 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
670 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
671 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
672 }
673
674 if (mSuppressedPointerIds != oldSuppressedIds) {
675 if (argsWithoutUnwantedPointers.size() != 1 ||
676 argsWithoutUnwantedPointers[0].pointerCount != args.pointerCount) {
677 ALOGI("Palm detected, removing pointer ids %s from %s",
678 dumpSet(mSuppressedPointerIds).c_str(), args.dump().c_str());
679 }
680 }
681
682 return argsWithoutUnwantedPointers;
683}
684
685const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() {
686 return mDeviceInfo;
687}
688
689std::string PalmRejector::dump() const {
690 std::string out;
691 out += "mDeviceInfo:\n";
692 out += addPrefix(dumpDeviceInfo(mDeviceInfo), " ");
693 out += "mSlotState:\n";
694 out += addPrefix(mSlotState.dump(), " ");
695 out += "mSuppressedPointerIds: ";
696 out += dumpSet(mSuppressedPointerIds) + "\n";
697 return out;
698}
699
700} // namespace android