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