blob: f904bfd82efb719ae0af81c9cd3210cdfcb9c59d [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 Vishniakouf6db4c32022-02-10 19:46:34 -0800371 ftl::StaticVector<NotifyMotionArgs, 2> 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);
412}
413
414void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
415 const NotifyPointerCaptureChangedArgs* args) {
416 mListener.notifyPointerCaptureChanged(args);
417}
418
419void UnwantedInteractionBlocker::notifyInputDevicesChanged(
420 const std::vector<InputDeviceInfo>& inputDevices) {
421 if (!mEnablePalmRejection) {
422 // Palm rejection is disabled. Don't create any palm rejector objects.
423 return;
424 }
425
426 // Let's see which of the existing devices didn't change, so that we can keep them
427 // and prevent event stream disruption
428 std::set<int32_t /*deviceId*/> devicesToKeep;
429 for (const InputDeviceInfo& device : inputDevices) {
430 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
431 if (!info) {
432 continue;
433 }
434
435 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
436 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
437 // Re-create the PalmRejector because the device info has changed.
438 mPalmRejectors.erase(it);
439 mPalmRejectors.emplace(device.getId(), *info);
440 }
441 devicesToKeep.insert(device.getId());
442 }
443 // Delete all devices that we don't need to keep
444 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
445 auto const& [deviceId, _] = item;
446 return devicesToKeep.find(deviceId) == devicesToKeep.end();
447 });
448}
449
450void UnwantedInteractionBlocker::dump(std::string& dump) {
451 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakouf6db4c32022-02-10 19:46:34 -0800452 dump += " mPreferStylusOverTouchBlocker:\n";
453 dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700454 dump += StringPrintf(" mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
455 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
456 toString(isPalmRejectionEnabled()));
457 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
458 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
459 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
460 dump += addPrefix(palmRejector.dump(), " ");
461 }
462}
463
464void UnwantedInteractionBlocker::monitor() {}
465
466UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
467
468void SlotState::update(const NotifyMotionArgs& args) {
469 for (size_t i = 0; i < args.pointerCount; i++) {
470 const int32_t pointerId = args.pointerProperties[i].id;
471 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
472 processPointerId(pointerId, resolvedAction);
473 }
474}
475
476size_t SlotState::findUnusedSlot() const {
477 size_t unusedSlot = 0;
478 // Since the collection is ordered, we can rely on the in-order traversal
479 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
480 if (unusedSlot != slot) {
481 break;
482 }
483 unusedSlot++;
484 }
485 return unusedSlot;
486}
487
488void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
489 switch (MotionEvent::getActionMasked(actionMasked)) {
490 case AMOTION_EVENT_ACTION_DOWN:
491 case AMOTION_EVENT_ACTION_POINTER_DOWN:
492 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
493 // New pointer going down
494 size_t newSlot = findUnusedSlot();
495 mPointerIdsBySlot[newSlot] = pointerId;
496 mSlotsByPointerId[pointerId] = newSlot;
497 return;
498 }
499 case AMOTION_EVENT_ACTION_MOVE:
500 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
501 return;
502 }
503 case AMOTION_EVENT_ACTION_CANCEL:
504 case AMOTION_EVENT_ACTION_POINTER_UP:
505 case AMOTION_EVENT_ACTION_UP:
506 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
507 auto it = mSlotsByPointerId.find(pointerId);
508 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
509 size_t slot = it->second;
510 // Erase this pointer from both collections
511 mPointerIdsBySlot.erase(slot);
512 mSlotsByPointerId.erase(pointerId);
513 return;
514 }
515 }
516 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
517 return;
518}
519
520std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
521 auto it = mSlotsByPointerId.find(pointerId);
522 if (it == mSlotsByPointerId.end()) {
523 return std::nullopt;
524 }
525 return it->second;
526}
527
528std::string SlotState::dump() const {
529 std::string out = "mSlotsByPointerId:\n";
530 out += addPrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
531 out += "mPointerIdsBySlot:\n";
532 out += addPrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
533 return out;
534}
535
536PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
537 std::unique_ptr<::ui::PalmDetectionFilter> filter)
538 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
539 mDeviceInfo(info),
540 mPalmDetectionFilter(std::move(filter)) {
541 if (mPalmDetectionFilter != nullptr) {
542 // This path is used for testing. Non-testing invocations should let this constructor
543 // create a real PalmDetectionFilter
544 return;
545 }
546 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
547 std::make_unique<::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel>(
548 std::vector<float>());
549 mPalmDetectionFilter =
550 std::make_unique<::ui::NeuralStylusPalmDetectionFilter>(mDeviceInfo, std::move(model),
551 mSharedPalmState.get());
552}
553
554std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
555 const AndroidPalmFilterDeviceInfo& deviceInfo,
556 const SlotState& oldSlotState,
557 const SlotState& newSlotState) {
558 std::vector<::ui::InProgressTouchEvdev> touches;
559
560 for (size_t i = 0; i < args.pointerCount; i++) {
561 const int32_t pointerId = args.pointerProperties[i].id;
562 touches.emplace_back(::ui::InProgressTouchEvdev());
563 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
564 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
565 touches.back().tool_type = getLinuxToolType(args.pointerProperties[i].toolType);
566
567 // Whether there is new information for the touch.
568 touches.back().altered = true;
569
570 // Whether the touch was cancelled. Touch events should be ignored till a
571 // new touch is initiated.
572 touches.back().was_cancelled = false;
573
574 // Whether the touch is going to be canceled.
575 touches.back().cancelled = false;
576
577 // Whether the touch is delayed at first appearance. Will not be reported yet.
578 touches.back().delayed = false;
579
580 // Whether the touch was delayed before.
581 touches.back().was_delayed = false;
582
583 // Whether the touch is held until end or no longer held.
584 touches.back().held = false;
585
586 // Whether this touch was held before being sent.
587 touches.back().was_held = false;
588
589 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
590 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
591 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
592 touches.back().was_touching = !isDown;
593
594 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
595 resolvedAction == AMOTION_EVENT_ACTION_UP ||
596 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
597
598 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
599 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
600
601 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
602 if (!slot) {
603 slot = oldSlotState.getSlotForPointerId(pointerId);
604 }
605 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
606 touches.back().slot = *slot;
607 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
608 touches.back().touching = !isUpOrCancel;
609
610 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
611 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
612 touches.back().tool_code = BTN_TOOL_FINGER;
613 // The field 'orientation' is not used for palm rejection
614 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
615 touches.back().reported_tool_type = ::ui::EventPointerType::kTouch;
616 touches.back().stylus_button = false;
617 }
618 return touches;
619}
620
621std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
622 if (mPalmDetectionFilter == nullptr) {
623 return {args};
624 }
625 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
626 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
627 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
628 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
629 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
630 args.action == AMOTION_EVENT_ACTION_SCROLL;
631 if (skipThisEvent) {
632 // Lets not process hover events, button events, or scroll for now.
633 return {args};
634 }
635 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
636 mSuppressedPointerIds.clear();
637 }
638 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
639 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
640
641 // Store the slot state before we call getTouches and update it. This way, we can find
642 // the slots that have been removed due to the incoming event.
643 SlotState oldSlotState = mSlotState;
644 mSlotState.update(args);
645 std::vector<::ui::InProgressTouchEvdev> touches =
646 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
647 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
648
649 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
650
651 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
652 std::set<int32_t> oldSuppressedIds;
653 std::swap(oldSuppressedIds, mSuppressedPointerIds);
654 for (size_t i = 0; i < args.pointerCount; i++) {
655 const int32_t pointerId = args.pointerProperties[i].id;
656 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
657 if (!slot) {
658 slot = mSlotState.getSlotForPointerId(pointerId);
659 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
660 }
661 if (slotsToSuppress.test(*slot)) {
662 mSuppressedPointerIds.insert(pointerId);
663 }
664 }
665
666 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
667 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
668 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
669 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
670 }
671
672 if (mSuppressedPointerIds != oldSuppressedIds) {
673 if (argsWithoutUnwantedPointers.size() != 1 ||
674 argsWithoutUnwantedPointers[0].pointerCount != args.pointerCount) {
675 ALOGI("Palm detected, removing pointer ids %s from %s",
676 dumpSet(mSuppressedPointerIds).c_str(), args.dump().c_str());
677 }
678 }
679
680 return argsWithoutUnwantedPointers;
681}
682
683const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() {
684 return mDeviceInfo;
685}
686
687std::string PalmRejector::dump() const {
688 std::string out;
689 out += "mDeviceInfo:\n";
690 out += addPrefix(dumpDeviceInfo(mDeviceInfo), " ");
691 out += "mSlotState:\n";
692 out += addPrefix(mSlotState.dump(), " ");
693 out += "mSuppressedPointerIds: ";
694 out += dumpSet(mSuppressedPointerIds) + "\n";
695 return out;
696}
697
698} // namespace android