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