blob: 0f6232477e0021021685e00affdd40bc0cd64a11 [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 Vishniakoub1230622023-09-19 09:06:56 -070021#include <com_android_input_flags.h>
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -070022#include <ftl/enum.h>
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -070023#include <input/PrintTools.h>
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070024#include <inttypes.h>
25#include <linux/input-event-codes.h>
26#include <linux/input.h>
27#include <server_configurable_flags/get_flags.h>
28
29#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
30#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
31
Siarhei Vishniakoub1230622023-09-19 09:06:56 -070032namespace input_flags = com::android::input::flags;
33
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070034using android::base::StringPrintf;
35
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -070036/**
37 * This type is declared here to ensure consistency between the instantiated type (used in the
38 * constructor via std::make_unique) and the cast-to type (used in PalmRejector::dump() with
39 * static_cast). Due to the lack of rtti support, dynamic_cast is not available, so this can't be
40 * checked at runtime to avoid undefined behaviour.
41 */
42using PalmFilterImplementation = ::ui::NeuralStylusPalmDetectionFilter;
43
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070044namespace android {
45
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +000046/**
47 * Log detailed debug messages about each inbound motion event notification to the blocker.
48 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerInboundMotion DEBUG"
49 * (requires restart)
50 */
51const bool DEBUG_INBOUND_MOTION =
52 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundMotion", ANDROID_LOG_INFO);
53
54/**
55 * Log detailed debug messages about each outbound motion event processed by the blocker.
56 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerOutboundMotion DEBUG"
57 * (requires restart)
58 */
59const bool DEBUG_OUTBOUND_MOTION =
60 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundMotion", ANDROID_LOG_INFO);
61
62/**
63 * Log the data sent to the model and received back from the model.
64 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerModel DEBUG"
65 * (requires restart)
66 */
67const bool DEBUG_MODEL =
68 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
69
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070070// Category (=namespace) name for the input settings that are applied at boot time
71static const char* INPUT_NATIVE_BOOT = "input_native_boot";
72/**
73 * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
74 * 'true' (not case sensitive) or '1'. To disable, specify any other value.
75 */
76static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
77
78static std::string toLower(std::string s) {
79 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
80 return s;
81}
82
83static bool isFromTouchscreen(int32_t source) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +000084 return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070085}
86
87static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
Siarhei Vishniakou229a8802022-07-28 21:58:56 +000088 return ::base::TimeTicks::UnixEpoch() + ::base::TimeDelta::FromNanosecondsD(eventTime);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070089}
90
91/**
92 * Return true if palm rejection is enabled via the server configurable flags. Return false
93 * otherwise.
94 */
95static bool isPalmRejectionEnabled() {
96 std::string value = toLower(
97 server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
Josep del Rio4b207792022-07-29 16:39:03 +000098 PALM_REJECTION_ENABLED, "0"));
99 if (value == "1") {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700100 return true;
101 }
102 return false;
103}
104
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700105static int getLinuxToolCode(ToolType toolType) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000106 switch (toolType) {
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700107 case ToolType::STYLUS:
Prabir Pradhane5626962022-10-27 20:30:53 +0000108 return BTN_TOOL_PEN;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700109 case ToolType::ERASER:
Prabir Pradhane5626962022-10-27 20:30:53 +0000110 return BTN_TOOL_RUBBER;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700111 case ToolType::FINGER:
Prabir Pradhane5626962022-10-27 20:30:53 +0000112 return BTN_TOOL_FINGER;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700113 case ToolType::UNKNOWN:
114 case ToolType::MOUSE:
115 case ToolType::PALM:
116 break;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700117 }
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700118 ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str());
119 return BTN_TOOL_FINGER;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700120}
121
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700122static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700123 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700124 if (pointerId == args.pointerProperties[i].id) {
125 return AMOTION_EVENT_ACTION_POINTER_UP |
126 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
127 }
128 }
129 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
130}
131
132/**
133 * Find the action for individual pointer at the given pointer index.
134 * This is always equal to MotionEvent::getActionMasked, except for
135 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
136 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
137 */
138static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
139 const int32_t actionMasked = MotionEvent::getActionMasked(action);
140 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
141 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
142 return actionMasked;
143 }
144 // This is a POINTER_DOWN or POINTER_UP event
145 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
146 if (pointerIndex == actionIndex) {
147 return actionMasked;
148 }
149 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
150 // pointers
151 return AMOTION_EVENT_ACTION_MOVE;
152}
153
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700154NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
155 const std::set<int32_t>& pointerIds) {
156 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
157 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
158 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
159 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
160
161 NotifyMotionArgs newArgs{args};
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700162 newArgs.pointerProperties.clear();
163 newArgs.pointerCoords.clear();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700164 int32_t newActionIndex = 0;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700165 for (uint32_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700166 const int32_t pointerId = args.pointerProperties[i].id;
167 if (pointerIds.find(pointerId) != pointerIds.end()) {
168 // skip this pointer
169 if (isPointerUpOrDownAction && i == actionIndex) {
170 // The active pointer is being removed, so the action is no longer valid.
171 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
172 // action later to a proper value.
173 newArgs.action = ACTION_UNKNOWN;
174 }
175 continue;
176 }
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700177 newArgs.pointerProperties.push_back(args.pointerProperties[i]);
178 newArgs.pointerCoords.push_back(args.pointerCoords[i]);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700179 if (i == actionIndex) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700180 newActionIndex = newArgs.getPointerCount() - 1;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700181 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700182 }
183 // Update POINTER_DOWN or POINTER_UP actions
184 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
185 newArgs.action =
186 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
187 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700188 if (newArgs.getPointerCount() == 1) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700189 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
190 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
191 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
192 newArgs.action = AMOTION_EVENT_ACTION_UP;
193 }
194 }
195 }
196 return newArgs;
197}
198
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000199/**
200 * Remove stylus pointers from the provided NotifyMotionArgs.
201 *
202 * Return NotifyMotionArgs where the stylus pointers have been removed.
203 * If this results in removal of the active pointer, then return nullopt.
204 */
205static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
206 std::set<int32_t> stylusPointerIds;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700207 for (uint32_t i = 0; i < args.getPointerCount(); i++) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000208 if (isStylusToolType(args.pointerProperties[i].toolType)) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000209 stylusPointerIds.insert(args.pointerProperties[i].id);
210 }
211 }
212 NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700213 if (withoutStylusPointers.getPointerCount() == 0 ||
214 withoutStylusPointers.action == ACTION_UNKNOWN) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000215 return std::nullopt;
216 }
217 return withoutStylusPointers;
218}
219
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700220std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
221 const InputDeviceInfo& deviceInfo) {
222 if (!isFromTouchscreen(deviceInfo.getSources())) {
223 return std::nullopt;
224 }
225 AndroidPalmFilterDeviceInfo out;
226 const InputDeviceInfo::MotionRange* axisX =
227 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
228 if (axisX != nullptr) {
229 out.max_x = axisX->max;
230 out.x_res = axisX->resolution;
231 } else {
232 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
233 deviceInfo.getDisplayName().c_str());
234 return std::nullopt;
235 }
236 const InputDeviceInfo::MotionRange* axisY =
237 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
238 if (axisY != nullptr) {
239 out.max_y = axisY->max;
240 out.y_res = axisY->resolution;
241 } else {
242 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
243 deviceInfo.getDisplayName().c_str());
244 return std::nullopt;
245 }
246 const InputDeviceInfo::MotionRange* axisMajor =
247 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
248 if (axisMajor != nullptr) {
249 out.major_radius_res = axisMajor->resolution;
250 out.touch_major_res = axisMajor->resolution;
251 } else {
252 return std::nullopt;
253 }
254 const InputDeviceInfo::MotionRange* axisMinor =
255 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
256 if (axisMinor != nullptr) {
257 out.minor_radius_res = axisMinor->resolution;
258 out.touch_minor_res = axisMinor->resolution;
259 out.minor_radius_supported = true;
260 } else {
261 out.minor_radius_supported = false;
262 }
263
264 return out;
265}
266
267/**
268 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
269 * that have already been canceled.
270 * The flow of the function is as follows:
271 * 1. Remove all already canceled pointers
272 * 2. Cancel all newly suppressed pointers
273 * 3. Decide what to do with the current event : keep it, or drop it
274 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
275 */
276std::vector<NotifyMotionArgs> cancelSuppressedPointers(
277 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
278 const std::set<int32_t>& newSuppressedPointerIds) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700279 LOG_ALWAYS_FATAL_IF(args.getPointerCount() == 0, "0 pointers in %s", args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700280
281 // First, let's remove the old suppressed pointers. They've already been canceled previously.
282 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
283
284 // Cancel any newly suppressed pointers.
285 std::vector<NotifyMotionArgs> out;
286 const int32_t activePointerId =
287 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
288 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
289 // We will iteratively remove pointers from 'removedArgs'.
290 NotifyMotionArgs removedArgs{oldArgs};
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700291 for (uint32_t i = 0; i < oldArgs.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700292 const int32_t pointerId = oldArgs.pointerProperties[i].id;
293 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
294 // This is a pointer that should not be canceled. Move on.
295 continue;
296 }
297 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
298 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
299 removedArgs = removePointerIds(removedArgs, {pointerId});
300 continue;
301 }
302
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700303 if (removedArgs.getPointerCount() == 1) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700304 // We are about to remove the last pointer, which means there will be no more gesture
305 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
306 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
307 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
308 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
309 return {oldArgs};
310 }
311 // Cancel the current pointer
312 out.push_back(removedArgs);
313 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
314 out.back().action = getActionUpForPointerId(out.back(), pointerId);
315
316 // Remove the newly canceled pointer from the args
317 removedArgs = removePointerIds(removedArgs, {pointerId});
318 }
319
320 // Now 'removedArgs' contains only pointers that are valid.
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700321 if (removedArgs.getPointerCount() <= 0 || removedArgs.action == ACTION_UNKNOWN) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700322 return out;
323 }
324 out.push_back(removedArgs);
325 return out;
326}
327
328UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
329 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
330
331UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
332 bool enablePalmRejection)
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700333 : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700334
335void UnwantedInteractionBlocker::notifyConfigurationChanged(
Prabir Pradhan678438e2023-04-13 19:32:51 +0000336 const NotifyConfigurationChangedArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700337 mQueuedListener.notifyConfigurationChanged(args);
338 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700339}
340
Prabir Pradhan678438e2023-04-13 19:32:51 +0000341void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700342 mQueuedListener.notifyKey(args);
343 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700344}
345
Prabir Pradhan678438e2023-04-13 19:32:51 +0000346void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
347 ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700348 { // acquire lock
349 std::scoped_lock lock(mLock);
Siarhei Vishniakoub1230622023-09-19 09:06:56 -0700350 if (input_flags::enable_multi_device_input()) {
351 notifyMotionLocked(args);
352 } else {
353 const std::vector<NotifyMotionArgs> processedArgs =
354 mPreferStylusOverTouchBlocker.processMotion(args);
355 for (const NotifyMotionArgs& loopArgs : processedArgs) {
356 notifyMotionLocked(loopArgs);
357 }
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700358 }
359 } // release lock
360
361 // Call out to the next stage without holding the lock
362 mQueuedListener.flush();
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800363}
364
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000365void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
366 ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
Prabir Pradhan678438e2023-04-13 19:32:51 +0000367 mQueuedListener.notifyMotion(args);
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000368}
369
Prabir Pradhan678438e2023-04-13 19:32:51 +0000370void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) {
371 auto it = mPalmRejectors.find(args.deviceId);
372 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700373 if (!sendToPalmRejector) {
Prabir Pradhan678438e2023-04-13 19:32:51 +0000374 enqueueOutboundMotionLocked(args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700375 return;
376 }
377
Prabir Pradhan678438e2023-04-13 19:32:51 +0000378 std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(args);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700379 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000380 enqueueOutboundMotionLocked(loopArgs);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700381 }
382}
383
Prabir Pradhan678438e2023-04-13 19:32:51 +0000384void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700385 mQueuedListener.notifySwitch(args);
386 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700387}
388
Prabir Pradhan678438e2023-04-13 19:32:51 +0000389void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700390 mQueuedListener.notifySensor(args);
391 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700392}
393
Prabir Pradhan678438e2023-04-13 19:32:51 +0000394void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700395 mQueuedListener.notifyVibratorState(args);
396 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700397}
Prabir Pradhan678438e2023-04-13 19:32:51 +0000398void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700399 { // acquire lock
400 std::scoped_lock lock(mLock);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000401 auto it = mPalmRejectors.find(args.deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700402 if (it != mPalmRejectors.end()) {
403 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
404 // Re-create the object instead of resetting it
405 mPalmRejectors.erase(it);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000406 mPalmRejectors.emplace(args.deviceId, info);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700407 }
408 mQueuedListener.notifyDeviceReset(args);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000409 mPreferStylusOverTouchBlocker.notifyDeviceReset(args);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700410 } // release lock
411 // Send events to the next stage without holding the lock
412 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700413}
414
415void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
Prabir Pradhan678438e2023-04-13 19:32:51 +0000416 const NotifyPointerCaptureChangedArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700417 mQueuedListener.notifyPointerCaptureChanged(args);
418 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700419}
420
421void UnwantedInteractionBlocker::notifyInputDevicesChanged(
Prabir Pradhane3da4bb2023-04-05 23:51:23 +0000422 const NotifyInputDevicesChangedArgs& args) {
423 onInputDevicesChanged(args.inputDeviceInfos);
424 mQueuedListener.notify(args);
425 mQueuedListener.flush();
426}
427
428void UnwantedInteractionBlocker::onInputDevicesChanged(
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700429 const std::vector<InputDeviceInfo>& inputDevices) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700430 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700431 if (!mEnablePalmRejection) {
432 // Palm rejection is disabled. Don't create any palm rejector objects.
433 return;
434 }
435
436 // Let's see which of the existing devices didn't change, so that we can keep them
437 // and prevent event stream disruption
438 std::set<int32_t /*deviceId*/> devicesToKeep;
439 for (const InputDeviceInfo& device : inputDevices) {
440 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
441 if (!info) {
442 continue;
443 }
444
445 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
446 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
447 // Re-create the PalmRejector because the device info has changed.
448 mPalmRejectors.erase(it);
449 mPalmRejectors.emplace(device.getId(), *info);
450 }
451 devicesToKeep.insert(device.getId());
452 }
453 // Delete all devices that we don't need to keep
454 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
455 auto const& [deviceId, _] = item;
456 return devicesToKeep.find(deviceId) == devicesToKeep.end();
457 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800458 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700459}
460
461void UnwantedInteractionBlocker::dump(std::string& dump) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700462 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700463 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800464 dump += " mPreferStylusOverTouchBlocker:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700465 dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700466 dump += StringPrintf(" mEnablePalmRejection: %s\n",
467 std::to_string(mEnablePalmRejection).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700468 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700469 std::to_string(isPalmRejectionEnabled()).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700470 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
471 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
472 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700473 dump += addLinePrefix(palmRejector.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700474 }
475}
476
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700477void UnwantedInteractionBlocker::monitor() {
478 std::scoped_lock lock(mLock);
479}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700480
481UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
482
483void SlotState::update(const NotifyMotionArgs& args) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700484 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700485 const int32_t pointerId = args.pointerProperties[i].id;
486 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
487 processPointerId(pointerId, resolvedAction);
488 }
489}
490
491size_t SlotState::findUnusedSlot() const {
492 size_t unusedSlot = 0;
493 // Since the collection is ordered, we can rely on the in-order traversal
494 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
495 if (unusedSlot != slot) {
496 break;
497 }
498 unusedSlot++;
499 }
500 return unusedSlot;
501}
502
503void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
504 switch (MotionEvent::getActionMasked(actionMasked)) {
505 case AMOTION_EVENT_ACTION_DOWN:
506 case AMOTION_EVENT_ACTION_POINTER_DOWN:
507 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
508 // New pointer going down
509 size_t newSlot = findUnusedSlot();
510 mPointerIdsBySlot[newSlot] = pointerId;
511 mSlotsByPointerId[pointerId] = newSlot;
512 return;
513 }
514 case AMOTION_EVENT_ACTION_MOVE:
515 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
516 return;
517 }
518 case AMOTION_EVENT_ACTION_CANCEL:
519 case AMOTION_EVENT_ACTION_POINTER_UP:
520 case AMOTION_EVENT_ACTION_UP:
521 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
522 auto it = mSlotsByPointerId.find(pointerId);
523 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
524 size_t slot = it->second;
525 // Erase this pointer from both collections
526 mPointerIdsBySlot.erase(slot);
527 mSlotsByPointerId.erase(pointerId);
528 return;
529 }
530 }
531 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
532 return;
533}
534
535std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
536 auto it = mSlotsByPointerId.find(pointerId);
537 if (it == mSlotsByPointerId.end()) {
538 return std::nullopt;
539 }
540 return it->second;
541}
542
543std::string SlotState::dump() const {
544 std::string out = "mSlotsByPointerId:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700545 out += addLinePrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700546 out += "mPointerIdsBySlot:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700547 out += addLinePrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700548 return out;
549}
550
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700551class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
552public:
553 AndroidPalmRejectionModel()
554 : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700555 std::vector<float>()) {
Siarhei Vishniakou5d673462022-07-08 10:47:57 -0700556 config_.resample_period = ::ui::kResamplePeriod;
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700557 }
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700558};
559
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700560PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
561 std::unique_ptr<::ui::PalmDetectionFilter> filter)
562 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
563 mDeviceInfo(info),
564 mPalmDetectionFilter(std::move(filter)) {
565 if (mPalmDetectionFilter != nullptr) {
566 // This path is used for testing. Non-testing invocations should let this constructor
567 // create a real PalmDetectionFilter
568 return;
569 }
570 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700571 std::make_unique<AndroidPalmRejectionModel>();
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700572 mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
573 mSharedPalmState.get());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700574}
575
576std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
577 const AndroidPalmFilterDeviceInfo& deviceInfo,
578 const SlotState& oldSlotState,
579 const SlotState& newSlotState) {
580 std::vector<::ui::InProgressTouchEvdev> touches;
581
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700582 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700583 const int32_t pointerId = args.pointerProperties[i].id;
584 touches.emplace_back(::ui::InProgressTouchEvdev());
585 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
586 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000587 // The field 'tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700588
589 // Whether there is new information for the touch.
590 touches.back().altered = true;
591
592 // Whether the touch was cancelled. Touch events should be ignored till a
593 // new touch is initiated.
594 touches.back().was_cancelled = false;
595
596 // Whether the touch is going to be canceled.
597 touches.back().cancelled = false;
598
599 // Whether the touch is delayed at first appearance. Will not be reported yet.
600 touches.back().delayed = false;
601
602 // Whether the touch was delayed before.
603 touches.back().was_delayed = false;
604
605 // Whether the touch is held until end or no longer held.
606 touches.back().held = false;
607
608 // Whether this touch was held before being sent.
609 touches.back().was_held = false;
610
611 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
612 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
613 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
614 touches.back().was_touching = !isDown;
615
616 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
617 resolvedAction == AMOTION_EVENT_ACTION_UP ||
618 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
619
620 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
621 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
622
623 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
624 if (!slot) {
625 slot = oldSlotState.getSlotForPointerId(pointerId);
626 }
627 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
628 touches.back().slot = *slot;
629 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
630 touches.back().touching = !isUpOrCancel;
631
632 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
633 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000634 touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700635 // The field 'orientation' is not used for palm rejection
636 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000637 // The field 'reported_tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700638 touches.back().stylus_button = false;
639 }
640 return touches;
641}
642
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000643std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
644 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
645 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
646
647 // Store the slot state before we call getTouches and update it. This way, we can find
648 // the slots that have been removed due to the incoming event.
649 SlotState oldSlotState = mSlotState;
650 mSlotState.update(args);
651
652 std::vector<::ui::InProgressTouchEvdev> touches =
653 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
654 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
655
656 if (DEBUG_MODEL) {
657 std::stringstream touchesStream;
658 for (const ::ui::InProgressTouchEvdev& touch : touches) {
659 touchesStream << touch.tracking_id << " : " << touch << "\n";
660 }
661 ALOGD("Filter: touches = %s", touchesStream.str().c_str());
662 }
663
664 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
665
666 ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
667 slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
668
669 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
670 std::set<int32_t> newSuppressedIds;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700671 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000672 const int32_t pointerId = args.pointerProperties[i].id;
673 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
674 if (!slot) {
675 slot = mSlotState.getSlotForPointerId(pointerId);
676 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
677 }
678 if (slotsToSuppress.test(*slot)) {
679 newSuppressedIds.insert(pointerId);
680 }
681 }
682 return newSuppressedIds;
683}
684
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700685std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
686 if (mPalmDetectionFilter == nullptr) {
687 return {args};
688 }
689 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
690 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
691 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
692 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
693 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
694 args.action == AMOTION_EVENT_ACTION_SCROLL;
695 if (skipThisEvent) {
696 // Lets not process hover events, button events, or scroll for now.
697 return {args};
698 }
699 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
700 mSuppressedPointerIds.clear();
701 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700702
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700703 std::set<int32_t> oldSuppressedIds;
704 std::swap(oldSuppressedIds, mSuppressedPointerIds);
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000705
706 std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
707 if (touchOnlyArgs) {
708 mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
709 } else {
710 // This is a stylus-only event.
711 // We can skip this event and just keep the suppressed pointer ids the same as before.
712 mSuppressedPointerIds = oldSuppressedIds;
713 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700714
715 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
716 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
717 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
718 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
719 }
720
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700721 // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
722 // subset of oldSuppressedIds.
723 if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
724 mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
725 ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
726 dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
727 args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700728 }
729
730 return argsWithoutUnwantedPointers;
731}
732
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000733const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700734 return mDeviceInfo;
735}
736
737std::string PalmRejector::dump() const {
738 std::string out;
739 out += "mDeviceInfo:\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700740 std::stringstream deviceInfo;
741 deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
742 << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
743 out += addLinePrefix(deviceInfo.str(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700744 out += "mSlotState:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700745 out += addLinePrefix(mSlotState.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700746 out += "mSuppressedPointerIds: ";
747 out += dumpSet(mSuppressedPointerIds) + "\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700748 std::stringstream state;
749 state << *mSharedPalmState;
750 out += "mSharedPalmState: " + state.str() + "\n";
751 std::stringstream filter;
752 filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
753 out += "mPalmDetectionFilter:\n";
754 out += addLinePrefix(filter.str(), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700755 return out;
756}
757
758} // namespace android