blob: f889de58b8c52238fa235e8fdd5d481733a707e3 [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 Vishniakou6d73f832022-07-21 17:27:03 -070021#include <ftl/enum.h>
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -070022#include <input/PrintTools.h>
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070023#include <inttypes.h>
24#include <linux/input-event-codes.h>
25#include <linux/input.h>
26#include <server_configurable_flags/get_flags.h>
27
28#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
29#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
30
31using android::base::StringPrintf;
32
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -070033/**
34 * This type is declared here to ensure consistency between the instantiated type (used in the
35 * constructor via std::make_unique) and the cast-to type (used in PalmRejector::dump() with
36 * static_cast). Due to the lack of rtti support, dynamic_cast is not available, so this can't be
37 * checked at runtime to avoid undefined behaviour.
38 */
39using PalmFilterImplementation = ::ui::NeuralStylusPalmDetectionFilter;
40
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070041namespace android {
42
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +000043/**
44 * Log detailed debug messages about each inbound motion event notification to the blocker.
45 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerInboundMotion DEBUG"
46 * (requires restart)
47 */
48const bool DEBUG_INBOUND_MOTION =
49 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundMotion", ANDROID_LOG_INFO);
50
51/**
52 * Log detailed debug messages about each outbound motion event processed by the blocker.
53 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerOutboundMotion DEBUG"
54 * (requires restart)
55 */
56const bool DEBUG_OUTBOUND_MOTION =
57 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundMotion", ANDROID_LOG_INFO);
58
59/**
60 * Log the data sent to the model and received back from the model.
61 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerModel DEBUG"
62 * (requires restart)
63 */
64const bool DEBUG_MODEL =
65 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
66
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070067// Category (=namespace) name for the input settings that are applied at boot time
68static const char* INPUT_NATIVE_BOOT = "input_native_boot";
69/**
70 * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
71 * 'true' (not case sensitive) or '1'. To disable, specify any other value.
72 */
73static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
74
75static std::string toLower(std::string s) {
76 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
77 return s;
78}
79
80static bool isFromTouchscreen(int32_t source) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +000081 return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070082}
83
84static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
Siarhei Vishniakou229a8802022-07-28 21:58:56 +000085 return ::base::TimeTicks::UnixEpoch() + ::base::TimeDelta::FromNanosecondsD(eventTime);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070086}
87
88/**
89 * Return true if palm rejection is enabled via the server configurable flags. Return false
90 * otherwise.
91 */
92static bool isPalmRejectionEnabled() {
93 std::string value = toLower(
94 server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
Josep del Rio4b207792022-07-29 16:39:03 +000095 PALM_REJECTION_ENABLED, "0"));
96 if (value == "1") {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070097 return true;
98 }
99 return false;
100}
101
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700102static int getLinuxToolCode(ToolType toolType) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000103 switch (toolType) {
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700104 case ToolType::STYLUS:
Prabir Pradhane5626962022-10-27 20:30:53 +0000105 return BTN_TOOL_PEN;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700106 case ToolType::ERASER:
Prabir Pradhane5626962022-10-27 20:30:53 +0000107 return BTN_TOOL_RUBBER;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700108 case ToolType::FINGER:
Prabir Pradhane5626962022-10-27 20:30:53 +0000109 return BTN_TOOL_FINGER;
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700110 case ToolType::UNKNOWN:
111 case ToolType::MOUSE:
112 case ToolType::PALM:
113 break;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700114 }
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -0700115 ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str());
116 return BTN_TOOL_FINGER;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700117}
118
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700119static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700120 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700121 if (pointerId == args.pointerProperties[i].id) {
122 return AMOTION_EVENT_ACTION_POINTER_UP |
123 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
124 }
125 }
126 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
127}
128
129/**
130 * Find the action for individual pointer at the given pointer index.
131 * This is always equal to MotionEvent::getActionMasked, except for
132 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
133 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
134 */
135static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
136 const int32_t actionMasked = MotionEvent::getActionMasked(action);
137 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
138 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
139 return actionMasked;
140 }
141 // This is a POINTER_DOWN or POINTER_UP event
142 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
143 if (pointerIndex == actionIndex) {
144 return actionMasked;
145 }
146 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
147 // pointers
148 return AMOTION_EVENT_ACTION_MOVE;
149}
150
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700151NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
152 const std::set<int32_t>& pointerIds) {
153 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
154 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
155 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
156 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
157
158 NotifyMotionArgs newArgs{args};
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700159 newArgs.pointerProperties.clear();
160 newArgs.pointerCoords.clear();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700161 int32_t newActionIndex = 0;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700162 for (uint32_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700163 const int32_t pointerId = args.pointerProperties[i].id;
164 if (pointerIds.find(pointerId) != pointerIds.end()) {
165 // skip this pointer
166 if (isPointerUpOrDownAction && i == actionIndex) {
167 // The active pointer is being removed, so the action is no longer valid.
168 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
169 // action later to a proper value.
170 newArgs.action = ACTION_UNKNOWN;
171 }
172 continue;
173 }
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700174 newArgs.pointerProperties.push_back(args.pointerProperties[i]);
175 newArgs.pointerCoords.push_back(args.pointerCoords[i]);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700176 if (i == actionIndex) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700177 newActionIndex = newArgs.getPointerCount() - 1;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700178 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700179 }
180 // Update POINTER_DOWN or POINTER_UP actions
181 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
182 newArgs.action =
183 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
184 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700185 if (newArgs.getPointerCount() == 1) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700186 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
187 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
188 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
189 newArgs.action = AMOTION_EVENT_ACTION_UP;
190 }
191 }
192 }
193 return newArgs;
194}
195
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000196/**
197 * Remove stylus pointers from the provided NotifyMotionArgs.
198 *
199 * Return NotifyMotionArgs where the stylus pointers have been removed.
200 * If this results in removal of the active pointer, then return nullopt.
201 */
202static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
203 std::set<int32_t> stylusPointerIds;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700204 for (uint32_t i = 0; i < args.getPointerCount(); i++) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000205 if (isStylusToolType(args.pointerProperties[i].toolType)) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000206 stylusPointerIds.insert(args.pointerProperties[i].id);
207 }
208 }
209 NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700210 if (withoutStylusPointers.getPointerCount() == 0 ||
211 withoutStylusPointers.action == ACTION_UNKNOWN) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000212 return std::nullopt;
213 }
214 return withoutStylusPointers;
215}
216
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700217std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
218 const InputDeviceInfo& deviceInfo) {
219 if (!isFromTouchscreen(deviceInfo.getSources())) {
220 return std::nullopt;
221 }
222 AndroidPalmFilterDeviceInfo out;
223 const InputDeviceInfo::MotionRange* axisX =
224 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
225 if (axisX != nullptr) {
226 out.max_x = axisX->max;
227 out.x_res = axisX->resolution;
228 } else {
229 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
230 deviceInfo.getDisplayName().c_str());
231 return std::nullopt;
232 }
233 const InputDeviceInfo::MotionRange* axisY =
234 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
235 if (axisY != nullptr) {
236 out.max_y = axisY->max;
237 out.y_res = axisY->resolution;
238 } else {
239 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
240 deviceInfo.getDisplayName().c_str());
241 return std::nullopt;
242 }
243 const InputDeviceInfo::MotionRange* axisMajor =
244 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
245 if (axisMajor != nullptr) {
246 out.major_radius_res = axisMajor->resolution;
247 out.touch_major_res = axisMajor->resolution;
248 } else {
249 return std::nullopt;
250 }
251 const InputDeviceInfo::MotionRange* axisMinor =
252 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
253 if (axisMinor != nullptr) {
254 out.minor_radius_res = axisMinor->resolution;
255 out.touch_minor_res = axisMinor->resolution;
256 out.minor_radius_supported = true;
257 } else {
258 out.minor_radius_supported = false;
259 }
260
261 return out;
262}
263
264/**
265 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
266 * that have already been canceled.
267 * The flow of the function is as follows:
268 * 1. Remove all already canceled pointers
269 * 2. Cancel all newly suppressed pointers
270 * 3. Decide what to do with the current event : keep it, or drop it
271 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
272 */
273std::vector<NotifyMotionArgs> cancelSuppressedPointers(
274 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
275 const std::set<int32_t>& newSuppressedPointerIds) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700276 LOG_ALWAYS_FATAL_IF(args.getPointerCount() == 0, "0 pointers in %s", args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700277
278 // First, let's remove the old suppressed pointers. They've already been canceled previously.
279 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
280
281 // Cancel any newly suppressed pointers.
282 std::vector<NotifyMotionArgs> out;
283 const int32_t activePointerId =
284 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
285 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
286 // We will iteratively remove pointers from 'removedArgs'.
287 NotifyMotionArgs removedArgs{oldArgs};
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700288 for (uint32_t i = 0; i < oldArgs.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700289 const int32_t pointerId = oldArgs.pointerProperties[i].id;
290 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
291 // This is a pointer that should not be canceled. Move on.
292 continue;
293 }
294 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
295 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
296 removedArgs = removePointerIds(removedArgs, {pointerId});
297 continue;
298 }
299
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700300 if (removedArgs.getPointerCount() == 1) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700301 // We are about to remove the last pointer, which means there will be no more gesture
302 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
303 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
304 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
305 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
306 return {oldArgs};
307 }
308 // Cancel the current pointer
309 out.push_back(removedArgs);
310 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
311 out.back().action = getActionUpForPointerId(out.back(), pointerId);
312
313 // Remove the newly canceled pointer from the args
314 removedArgs = removePointerIds(removedArgs, {pointerId});
315 }
316
317 // Now 'removedArgs' contains only pointers that are valid.
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700318 if (removedArgs.getPointerCount() <= 0 || removedArgs.action == ACTION_UNKNOWN) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700319 return out;
320 }
321 out.push_back(removedArgs);
322 return out;
323}
324
325UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
326 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
327
328UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
329 bool enablePalmRejection)
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700330 : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700331
332void UnwantedInteractionBlocker::notifyConfigurationChanged(
Prabir Pradhan678438e2023-04-13 19:32:51 +0000333 const NotifyConfigurationChangedArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700334 mQueuedListener.notifyConfigurationChanged(args);
335 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700336}
337
Prabir Pradhan678438e2023-04-13 19:32:51 +0000338void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700339 mQueuedListener.notifyKey(args);
340 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700341}
342
Prabir Pradhan678438e2023-04-13 19:32:51 +0000343void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
344 ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700345 { // acquire lock
346 std::scoped_lock lock(mLock);
347 const std::vector<NotifyMotionArgs> processedArgs =
Prabir Pradhan678438e2023-04-13 19:32:51 +0000348 mPreferStylusOverTouchBlocker.processMotion(args);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700349 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Prabir Pradhan678438e2023-04-13 19:32:51 +0000350 notifyMotionLocked(loopArgs);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700351 }
352 } // release lock
353
354 // Call out to the next stage without holding the lock
355 mQueuedListener.flush();
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800356}
357
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000358void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
359 ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
Prabir Pradhan678438e2023-04-13 19:32:51 +0000360 mQueuedListener.notifyMotion(args);
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000361}
362
Prabir Pradhan678438e2023-04-13 19:32:51 +0000363void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) {
364 auto it = mPalmRejectors.find(args.deviceId);
365 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700366 if (!sendToPalmRejector) {
Prabir Pradhan678438e2023-04-13 19:32:51 +0000367 enqueueOutboundMotionLocked(args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700368 return;
369 }
370
Prabir Pradhan678438e2023-04-13 19:32:51 +0000371 std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(args);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700372 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000373 enqueueOutboundMotionLocked(loopArgs);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700374 }
375}
376
Prabir Pradhan678438e2023-04-13 19:32:51 +0000377void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700378 mQueuedListener.notifySwitch(args);
379 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700380}
381
Prabir Pradhan678438e2023-04-13 19:32:51 +0000382void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700383 mQueuedListener.notifySensor(args);
384 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700385}
386
Prabir Pradhan678438e2023-04-13 19:32:51 +0000387void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700388 mQueuedListener.notifyVibratorState(args);
389 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700390}
Prabir Pradhan678438e2023-04-13 19:32:51 +0000391void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700392 { // acquire lock
393 std::scoped_lock lock(mLock);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000394 auto it = mPalmRejectors.find(args.deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700395 if (it != mPalmRejectors.end()) {
396 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
397 // Re-create the object instead of resetting it
398 mPalmRejectors.erase(it);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000399 mPalmRejectors.emplace(args.deviceId, info);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700400 }
401 mQueuedListener.notifyDeviceReset(args);
Prabir Pradhan678438e2023-04-13 19:32:51 +0000402 mPreferStylusOverTouchBlocker.notifyDeviceReset(args);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700403 } // release lock
404 // Send events to the next stage without holding the lock
405 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700406}
407
408void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
Prabir Pradhan678438e2023-04-13 19:32:51 +0000409 const NotifyPointerCaptureChangedArgs& args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700410 mQueuedListener.notifyPointerCaptureChanged(args);
411 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700412}
413
414void UnwantedInteractionBlocker::notifyInputDevicesChanged(
Prabir Pradhane3da4bb2023-04-05 23:51:23 +0000415 const NotifyInputDevicesChangedArgs& args) {
416 onInputDevicesChanged(args.inputDeviceInfos);
417 mQueuedListener.notify(args);
418 mQueuedListener.flush();
419}
420
421void UnwantedInteractionBlocker::onInputDevicesChanged(
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700422 const std::vector<InputDeviceInfo>& inputDevices) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700423 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700424 if (!mEnablePalmRejection) {
425 // Palm rejection is disabled. Don't create any palm rejector objects.
426 return;
427 }
428
429 // Let's see which of the existing devices didn't change, so that we can keep them
430 // and prevent event stream disruption
431 std::set<int32_t /*deviceId*/> devicesToKeep;
432 for (const InputDeviceInfo& device : inputDevices) {
433 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
434 if (!info) {
435 continue;
436 }
437
438 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
439 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
440 // Re-create the PalmRejector because the device info has changed.
441 mPalmRejectors.erase(it);
442 mPalmRejectors.emplace(device.getId(), *info);
443 }
444 devicesToKeep.insert(device.getId());
445 }
446 // Delete all devices that we don't need to keep
447 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
448 auto const& [deviceId, _] = item;
449 return devicesToKeep.find(deviceId) == devicesToKeep.end();
450 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800451 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700452}
453
454void UnwantedInteractionBlocker::dump(std::string& dump) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700455 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700456 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800457 dump += " mPreferStylusOverTouchBlocker:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700458 dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700459 dump += StringPrintf(" mEnablePalmRejection: %s\n",
460 std::to_string(mEnablePalmRejection).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700461 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700462 std::to_string(isPalmRejectionEnabled()).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700463 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
464 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
465 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700466 dump += addLinePrefix(palmRejector.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700467 }
468}
469
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700470void UnwantedInteractionBlocker::monitor() {
471 std::scoped_lock lock(mLock);
472}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700473
474UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
475
476void SlotState::update(const NotifyMotionArgs& args) {
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700477 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700478 const int32_t pointerId = args.pointerProperties[i].id;
479 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
480 processPointerId(pointerId, resolvedAction);
481 }
482}
483
484size_t SlotState::findUnusedSlot() const {
485 size_t unusedSlot = 0;
486 // Since the collection is ordered, we can rely on the in-order traversal
487 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
488 if (unusedSlot != slot) {
489 break;
490 }
491 unusedSlot++;
492 }
493 return unusedSlot;
494}
495
496void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
497 switch (MotionEvent::getActionMasked(actionMasked)) {
498 case AMOTION_EVENT_ACTION_DOWN:
499 case AMOTION_EVENT_ACTION_POINTER_DOWN:
500 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
501 // New pointer going down
502 size_t newSlot = findUnusedSlot();
503 mPointerIdsBySlot[newSlot] = pointerId;
504 mSlotsByPointerId[pointerId] = newSlot;
505 return;
506 }
507 case AMOTION_EVENT_ACTION_MOVE:
508 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
509 return;
510 }
511 case AMOTION_EVENT_ACTION_CANCEL:
512 case AMOTION_EVENT_ACTION_POINTER_UP:
513 case AMOTION_EVENT_ACTION_UP:
514 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
515 auto it = mSlotsByPointerId.find(pointerId);
516 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
517 size_t slot = it->second;
518 // Erase this pointer from both collections
519 mPointerIdsBySlot.erase(slot);
520 mSlotsByPointerId.erase(pointerId);
521 return;
522 }
523 }
524 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
525 return;
526}
527
528std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
529 auto it = mSlotsByPointerId.find(pointerId);
530 if (it == mSlotsByPointerId.end()) {
531 return std::nullopt;
532 }
533 return it->second;
534}
535
536std::string SlotState::dump() const {
537 std::string out = "mSlotsByPointerId:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700538 out += addLinePrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700539 out += "mPointerIdsBySlot:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700540 out += addLinePrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700541 return out;
542}
543
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700544class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
545public:
546 AndroidPalmRejectionModel()
547 : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700548 std::vector<float>()) {
Siarhei Vishniakou5d673462022-07-08 10:47:57 -0700549 config_.resample_period = ::ui::kResamplePeriod;
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700550 }
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700551};
552
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700553PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
554 std::unique_ptr<::ui::PalmDetectionFilter> filter)
555 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
556 mDeviceInfo(info),
557 mPalmDetectionFilter(std::move(filter)) {
558 if (mPalmDetectionFilter != nullptr) {
559 // This path is used for testing. Non-testing invocations should let this constructor
560 // create a real PalmDetectionFilter
561 return;
562 }
563 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700564 std::make_unique<AndroidPalmRejectionModel>();
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700565 mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
566 mSharedPalmState.get());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700567}
568
569std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
570 const AndroidPalmFilterDeviceInfo& deviceInfo,
571 const SlotState& oldSlotState,
572 const SlotState& newSlotState) {
573 std::vector<::ui::InProgressTouchEvdev> touches;
574
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700575 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700576 const int32_t pointerId = args.pointerProperties[i].id;
577 touches.emplace_back(::ui::InProgressTouchEvdev());
578 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
579 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000580 // The field 'tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700581
582 // Whether there is new information for the touch.
583 touches.back().altered = true;
584
585 // Whether the touch was cancelled. Touch events should be ignored till a
586 // new touch is initiated.
587 touches.back().was_cancelled = false;
588
589 // Whether the touch is going to be canceled.
590 touches.back().cancelled = false;
591
592 // Whether the touch is delayed at first appearance. Will not be reported yet.
593 touches.back().delayed = false;
594
595 // Whether the touch was delayed before.
596 touches.back().was_delayed = false;
597
598 // Whether the touch is held until end or no longer held.
599 touches.back().held = false;
600
601 // Whether this touch was held before being sent.
602 touches.back().was_held = false;
603
604 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
605 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
606 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
607 touches.back().was_touching = !isDown;
608
609 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
610 resolvedAction == AMOTION_EVENT_ACTION_UP ||
611 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
612
613 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
614 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
615
616 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
617 if (!slot) {
618 slot = oldSlotState.getSlotForPointerId(pointerId);
619 }
620 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
621 touches.back().slot = *slot;
622 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
623 touches.back().touching = !isUpOrCancel;
624
625 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
626 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000627 touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700628 // The field 'orientation' is not used for palm rejection
629 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000630 // The field 'reported_tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700631 touches.back().stylus_button = false;
632 }
633 return touches;
634}
635
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000636std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
637 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
638 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
639
640 // Store the slot state before we call getTouches and update it. This way, we can find
641 // the slots that have been removed due to the incoming event.
642 SlotState oldSlotState = mSlotState;
643 mSlotState.update(args);
644
645 std::vector<::ui::InProgressTouchEvdev> touches =
646 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
647 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
648
649 if (DEBUG_MODEL) {
650 std::stringstream touchesStream;
651 for (const ::ui::InProgressTouchEvdev& touch : touches) {
652 touchesStream << touch.tracking_id << " : " << touch << "\n";
653 }
654 ALOGD("Filter: touches = %s", touchesStream.str().c_str());
655 }
656
657 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
658
659 ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
660 slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
661
662 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
663 std::set<int32_t> newSuppressedIds;
Siarhei Vishniakou3218fc02023-06-15 20:41:02 -0700664 for (size_t i = 0; i < args.getPointerCount(); i++) {
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000665 const int32_t pointerId = args.pointerProperties[i].id;
666 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
667 if (!slot) {
668 slot = mSlotState.getSlotForPointerId(pointerId);
669 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
670 }
671 if (slotsToSuppress.test(*slot)) {
672 newSuppressedIds.insert(pointerId);
673 }
674 }
675 return newSuppressedIds;
676}
677
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700678std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
679 if (mPalmDetectionFilter == nullptr) {
680 return {args};
681 }
682 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
683 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
684 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
685 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
686 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
687 args.action == AMOTION_EVENT_ACTION_SCROLL;
688 if (skipThisEvent) {
689 // Lets not process hover events, button events, or scroll for now.
690 return {args};
691 }
692 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
693 mSuppressedPointerIds.clear();
694 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700695
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700696 std::set<int32_t> oldSuppressedIds;
697 std::swap(oldSuppressedIds, mSuppressedPointerIds);
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000698
699 std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
700 if (touchOnlyArgs) {
701 mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
702 } else {
703 // This is a stylus-only event.
704 // We can skip this event and just keep the suppressed pointer ids the same as before.
705 mSuppressedPointerIds = oldSuppressedIds;
706 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700707
708 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
709 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
710 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
711 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
712 }
713
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700714 // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
715 // subset of oldSuppressedIds.
716 if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
717 mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
718 ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
719 dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
720 args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700721 }
722
723 return argsWithoutUnwantedPointers;
724}
725
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000726const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700727 return mDeviceInfo;
728}
729
730std::string PalmRejector::dump() const {
731 std::string out;
732 out += "mDeviceInfo:\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700733 std::stringstream deviceInfo;
734 deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
735 << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
736 out += addLinePrefix(deviceInfo.str(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700737 out += "mSlotState:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700738 out += addLinePrefix(mSlotState.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700739 out += "mSuppressedPointerIds: ";
740 out += dumpSet(mSuppressedPointerIds) + "\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700741 std::stringstream state;
742 state << *mSharedPalmState;
743 out += "mSharedPalmState: " + state.str() + "\n";
744 std::stringstream filter;
745 filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
746 out += "mPalmDetectionFilter:\n";
747 out += addLinePrefix(filter.str(), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700748 return out;
749}
750
751} // namespace android