blob: fc8dfe678b60a2a8a8eb19edb291b2169d5ab132 [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
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +000042/**
43 * Log detailed debug messages about each inbound motion event notification to the blocker.
44 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerInboundMotion DEBUG"
45 * (requires restart)
46 */
47const bool DEBUG_INBOUND_MOTION =
48 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundMotion", ANDROID_LOG_INFO);
49
50/**
51 * Log detailed debug messages about each outbound motion event processed by the blocker.
52 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerOutboundMotion DEBUG"
53 * (requires restart)
54 */
55const bool DEBUG_OUTBOUND_MOTION =
56 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundMotion", ANDROID_LOG_INFO);
57
58/**
59 * Log the data sent to the model and received back from the model.
60 * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerModel DEBUG"
61 * (requires restart)
62 */
63const bool DEBUG_MODEL =
64 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
65
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070066// Category (=namespace) name for the input settings that are applied at boot time
67static const char* INPUT_NATIVE_BOOT = "input_native_boot";
68/**
69 * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
70 * 'true' (not case sensitive) or '1'. To disable, specify any other value.
71 */
72static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
73
74static std::string toLower(std::string s) {
75 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
76 return s;
77}
78
79static bool isFromTouchscreen(int32_t source) {
Siarhei Vishniakouf6db4c32022-02-10 19:46:34 -080080 return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN) &&
81 !isFromSource(source, AINPUT_SOURCE_STYLUS);
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 Vishniakou88151b82022-08-11 00:53:38 +0000102static int getLinuxToolCode(int toolType) {
103 if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
104 return BTN_TOOL_PEN;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700105 }
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000106 if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
107 return BTN_TOOL_FINGER;
108 }
109 ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
110 return BTN_TOOL_FINGER;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700111}
112
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700113static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
114 for (size_t i = 0; i < args.pointerCount; i++) {
115 if (pointerId == args.pointerProperties[i].id) {
116 return AMOTION_EVENT_ACTION_POINTER_UP |
117 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
118 }
119 }
120 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
121}
122
123/**
124 * Find the action for individual pointer at the given pointer index.
125 * This is always equal to MotionEvent::getActionMasked, except for
126 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
127 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
128 */
129static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
130 const int32_t actionMasked = MotionEvent::getActionMasked(action);
131 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
132 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
133 return actionMasked;
134 }
135 // This is a POINTER_DOWN or POINTER_UP event
136 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
137 if (pointerIndex == actionIndex) {
138 return actionMasked;
139 }
140 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
141 // pointers
142 return AMOTION_EVENT_ACTION_MOVE;
143}
144
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700145/**
146 * Remove the data for the provided pointers from the args. The pointers are identified by their
147 * pointerId, not by the index inside the array.
148 * Return the new NotifyMotionArgs struct that has the remaining pointers.
149 * The only fields that may be different in the returned args from the provided args are:
150 * - action
151 * - pointerCount
152 * - pointerProperties
153 * - pointerCoords
154 * Action might change because it contains a pointer index. If another pointer is removed, the
155 * active pointer index would be shifted.
156 * Do not call this function for events with POINTER_UP or POINTER_DOWN events when removed pointer
157 * id is the acting pointer id.
158 *
159 * @param args the args from which the pointers should be removed
160 * @param pointerIds the pointer ids of the pointers that should be removed
161 */
162NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
163 const std::set<int32_t>& pointerIds) {
164 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
165 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
166 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
167 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
168
169 NotifyMotionArgs newArgs{args};
170 newArgs.pointerCount = 0;
171 int32_t newActionIndex = 0;
172 for (uint32_t i = 0; i < args.pointerCount; i++) {
173 const int32_t pointerId = args.pointerProperties[i].id;
174 if (pointerIds.find(pointerId) != pointerIds.end()) {
175 // skip this pointer
176 if (isPointerUpOrDownAction && i == actionIndex) {
177 // The active pointer is being removed, so the action is no longer valid.
178 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
179 // action later to a proper value.
180 newArgs.action = ACTION_UNKNOWN;
181 }
182 continue;
183 }
184 newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
185 newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
186 if (i == actionIndex) {
187 newActionIndex = newArgs.pointerCount;
188 }
189 newArgs.pointerCount++;
190 }
191 // Update POINTER_DOWN or POINTER_UP actions
192 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
193 newArgs.action =
194 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
195 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
196 if (newArgs.pointerCount == 1) {
197 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
198 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
199 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
200 newArgs.action = AMOTION_EVENT_ACTION_UP;
201 }
202 }
203 }
204 return newArgs;
205}
206
207std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
208 const InputDeviceInfo& deviceInfo) {
209 if (!isFromTouchscreen(deviceInfo.getSources())) {
210 return std::nullopt;
211 }
212 AndroidPalmFilterDeviceInfo out;
213 const InputDeviceInfo::MotionRange* axisX =
214 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
215 if (axisX != nullptr) {
216 out.max_x = axisX->max;
217 out.x_res = axisX->resolution;
218 } else {
219 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
220 deviceInfo.getDisplayName().c_str());
221 return std::nullopt;
222 }
223 const InputDeviceInfo::MotionRange* axisY =
224 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
225 if (axisY != nullptr) {
226 out.max_y = axisY->max;
227 out.y_res = axisY->resolution;
228 } else {
229 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
230 deviceInfo.getDisplayName().c_str());
231 return std::nullopt;
232 }
233 const InputDeviceInfo::MotionRange* axisMajor =
234 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
235 if (axisMajor != nullptr) {
236 out.major_radius_res = axisMajor->resolution;
237 out.touch_major_res = axisMajor->resolution;
238 } else {
239 return std::nullopt;
240 }
241 const InputDeviceInfo::MotionRange* axisMinor =
242 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
243 if (axisMinor != nullptr) {
244 out.minor_radius_res = axisMinor->resolution;
245 out.touch_minor_res = axisMinor->resolution;
246 out.minor_radius_supported = true;
247 } else {
248 out.minor_radius_supported = false;
249 }
250
251 return out;
252}
253
254/**
255 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
256 * that have already been canceled.
257 * The flow of the function is as follows:
258 * 1. Remove all already canceled pointers
259 * 2. Cancel all newly suppressed pointers
260 * 3. Decide what to do with the current event : keep it, or drop it
261 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
262 */
263std::vector<NotifyMotionArgs> cancelSuppressedPointers(
264 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
265 const std::set<int32_t>& newSuppressedPointerIds) {
266 LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
267
268 // First, let's remove the old suppressed pointers. They've already been canceled previously.
269 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
270
271 // Cancel any newly suppressed pointers.
272 std::vector<NotifyMotionArgs> out;
273 const int32_t activePointerId =
274 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
275 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
276 // We will iteratively remove pointers from 'removedArgs'.
277 NotifyMotionArgs removedArgs{oldArgs};
278 for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
279 const int32_t pointerId = oldArgs.pointerProperties[i].id;
280 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
281 // This is a pointer that should not be canceled. Move on.
282 continue;
283 }
284 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
285 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
286 removedArgs = removePointerIds(removedArgs, {pointerId});
287 continue;
288 }
289
290 if (removedArgs.pointerCount == 1) {
291 // We are about to remove the last pointer, which means there will be no more gesture
292 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
293 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
294 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
295 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
296 return {oldArgs};
297 }
298 // Cancel the current pointer
299 out.push_back(removedArgs);
300 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
301 out.back().action = getActionUpForPointerId(out.back(), pointerId);
302
303 // Remove the newly canceled pointer from the args
304 removedArgs = removePointerIds(removedArgs, {pointerId});
305 }
306
307 // Now 'removedArgs' contains only pointers that are valid.
308 if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
309 return out;
310 }
311 out.push_back(removedArgs);
312 return out;
313}
314
315UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
316 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
317
318UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
319 bool enablePalmRejection)
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700320 : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700321
322void UnwantedInteractionBlocker::notifyConfigurationChanged(
323 const NotifyConfigurationChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700324 mQueuedListener.notifyConfigurationChanged(args);
325 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700326}
327
328void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700329 mQueuedListener.notifyKey(args);
330 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700331}
332
333void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000334 ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700335 { // acquire lock
336 std::scoped_lock lock(mLock);
337 const std::vector<NotifyMotionArgs> processedArgs =
338 mPreferStylusOverTouchBlocker.processMotion(*args);
339 for (const NotifyMotionArgs& loopArgs : processedArgs) {
340 notifyMotionLocked(&loopArgs);
341 }
342 } // release lock
343
344 // Call out to the next stage without holding the lock
345 mQueuedListener.flush();
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800346}
347
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000348void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
349 ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
350 mQueuedListener.notifyMotion(&args);
351}
352
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700353void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700354 auto it = mPalmRejectors.find(args->deviceId);
355 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
356 if (!sendToPalmRejector) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000357 enqueueOutboundMotionLocked(*args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700358 return;
359 }
360
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700361 std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
362 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000363 enqueueOutboundMotionLocked(loopArgs);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700364 }
365}
366
367void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700368 mQueuedListener.notifySwitch(args);
369 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700370}
371
372void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700373 mQueuedListener.notifySensor(args);
374 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700375}
376
377void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700378 mQueuedListener.notifyVibratorState(args);
379 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700380}
381void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700382 { // acquire lock
383 std::scoped_lock lock(mLock);
384 auto it = mPalmRejectors.find(args->deviceId);
385 if (it != mPalmRejectors.end()) {
386 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
387 // Re-create the object instead of resetting it
388 mPalmRejectors.erase(it);
389 mPalmRejectors.emplace(args->deviceId, info);
390 }
391 mQueuedListener.notifyDeviceReset(args);
392 mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
393 } // release lock
394 // Send events to the next stage without holding the lock
395 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700396}
397
398void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
399 const NotifyPointerCaptureChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700400 mQueuedListener.notifyPointerCaptureChanged(args);
401 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700402}
403
404void UnwantedInteractionBlocker::notifyInputDevicesChanged(
405 const std::vector<InputDeviceInfo>& inputDevices) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700406 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700407 if (!mEnablePalmRejection) {
408 // Palm rejection is disabled. Don't create any palm rejector objects.
409 return;
410 }
411
412 // Let's see which of the existing devices didn't change, so that we can keep them
413 // and prevent event stream disruption
414 std::set<int32_t /*deviceId*/> devicesToKeep;
415 for (const InputDeviceInfo& device : inputDevices) {
416 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
417 if (!info) {
418 continue;
419 }
420
421 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
422 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
423 // Re-create the PalmRejector because the device info has changed.
424 mPalmRejectors.erase(it);
425 mPalmRejectors.emplace(device.getId(), *info);
426 }
427 devicesToKeep.insert(device.getId());
428 }
429 // Delete all devices that we don't need to keep
430 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
431 auto const& [deviceId, _] = item;
432 return devicesToKeep.find(deviceId) == devicesToKeep.end();
433 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800434 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700435}
436
437void UnwantedInteractionBlocker::dump(std::string& dump) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700438 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700439 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800440 dump += " mPreferStylusOverTouchBlocker:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700441 dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700442 dump += StringPrintf(" mEnablePalmRejection: %s\n",
443 std::to_string(mEnablePalmRejection).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700444 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700445 std::to_string(isPalmRejectionEnabled()).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700446 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
447 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
448 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700449 dump += addLinePrefix(palmRejector.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700450 }
451}
452
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700453void UnwantedInteractionBlocker::monitor() {
454 std::scoped_lock lock(mLock);
455}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700456
457UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
458
459void SlotState::update(const NotifyMotionArgs& args) {
460 for (size_t i = 0; i < args.pointerCount; i++) {
461 const int32_t pointerId = args.pointerProperties[i].id;
462 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
463 processPointerId(pointerId, resolvedAction);
464 }
465}
466
467size_t SlotState::findUnusedSlot() const {
468 size_t unusedSlot = 0;
469 // Since the collection is ordered, we can rely on the in-order traversal
470 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
471 if (unusedSlot != slot) {
472 break;
473 }
474 unusedSlot++;
475 }
476 return unusedSlot;
477}
478
479void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
480 switch (MotionEvent::getActionMasked(actionMasked)) {
481 case AMOTION_EVENT_ACTION_DOWN:
482 case AMOTION_EVENT_ACTION_POINTER_DOWN:
483 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
484 // New pointer going down
485 size_t newSlot = findUnusedSlot();
486 mPointerIdsBySlot[newSlot] = pointerId;
487 mSlotsByPointerId[pointerId] = newSlot;
488 return;
489 }
490 case AMOTION_EVENT_ACTION_MOVE:
491 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
492 return;
493 }
494 case AMOTION_EVENT_ACTION_CANCEL:
495 case AMOTION_EVENT_ACTION_POINTER_UP:
496 case AMOTION_EVENT_ACTION_UP:
497 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
498 auto it = mSlotsByPointerId.find(pointerId);
499 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
500 size_t slot = it->second;
501 // Erase this pointer from both collections
502 mPointerIdsBySlot.erase(slot);
503 mSlotsByPointerId.erase(pointerId);
504 return;
505 }
506 }
507 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
508 return;
509}
510
511std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
512 auto it = mSlotsByPointerId.find(pointerId);
513 if (it == mSlotsByPointerId.end()) {
514 return std::nullopt;
515 }
516 return it->second;
517}
518
519std::string SlotState::dump() const {
520 std::string out = "mSlotsByPointerId:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700521 out += addLinePrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700522 out += "mPointerIdsBySlot:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700523 out += addLinePrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700524 return out;
525}
526
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700527class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
528public:
529 AndroidPalmRejectionModel()
530 : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700531 std::vector<float>()) {
Siarhei Vishniakou5d673462022-07-08 10:47:57 -0700532 config_.resample_period = ::ui::kResamplePeriod;
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700533 }
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700534};
535
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700536PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
537 std::unique_ptr<::ui::PalmDetectionFilter> filter)
538 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
539 mDeviceInfo(info),
540 mPalmDetectionFilter(std::move(filter)) {
541 if (mPalmDetectionFilter != nullptr) {
542 // This path is used for testing. Non-testing invocations should let this constructor
543 // create a real PalmDetectionFilter
544 return;
545 }
546 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700547 std::make_unique<AndroidPalmRejectionModel>();
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700548 mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
549 mSharedPalmState.get());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700550}
551
552std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
553 const AndroidPalmFilterDeviceInfo& deviceInfo,
554 const SlotState& oldSlotState,
555 const SlotState& newSlotState) {
556 std::vector<::ui::InProgressTouchEvdev> touches;
557
558 for (size_t i = 0; i < args.pointerCount; i++) {
559 const int32_t pointerId = args.pointerProperties[i].id;
560 touches.emplace_back(::ui::InProgressTouchEvdev());
561 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
562 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000563 // The field 'tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700564
565 // Whether there is new information for the touch.
566 touches.back().altered = true;
567
568 // Whether the touch was cancelled. Touch events should be ignored till a
569 // new touch is initiated.
570 touches.back().was_cancelled = false;
571
572 // Whether the touch is going to be canceled.
573 touches.back().cancelled = false;
574
575 // Whether the touch is delayed at first appearance. Will not be reported yet.
576 touches.back().delayed = false;
577
578 // Whether the touch was delayed before.
579 touches.back().was_delayed = false;
580
581 // Whether the touch is held until end or no longer held.
582 touches.back().held = false;
583
584 // Whether this touch was held before being sent.
585 touches.back().was_held = false;
586
587 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
588 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
589 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
590 touches.back().was_touching = !isDown;
591
592 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
593 resolvedAction == AMOTION_EVENT_ACTION_UP ||
594 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
595
596 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
597 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
598
599 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
600 if (!slot) {
601 slot = oldSlotState.getSlotForPointerId(pointerId);
602 }
603 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
604 touches.back().slot = *slot;
605 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
606 touches.back().touching = !isUpOrCancel;
607
608 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
609 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000610 touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700611 // The field 'orientation' is not used for palm rejection
612 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000613 // The field 'reported_tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700614 touches.back().stylus_button = false;
615 }
616 return touches;
617}
618
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000619std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
620 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
621 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
622
623 // Store the slot state before we call getTouches and update it. This way, we can find
624 // the slots that have been removed due to the incoming event.
625 SlotState oldSlotState = mSlotState;
626 mSlotState.update(args);
627
628 std::vector<::ui::InProgressTouchEvdev> touches =
629 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
630 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
631
632 if (DEBUG_MODEL) {
633 std::stringstream touchesStream;
634 for (const ::ui::InProgressTouchEvdev& touch : touches) {
635 touchesStream << touch.tracking_id << " : " << touch << "\n";
636 }
637 ALOGD("Filter: touches = %s", touchesStream.str().c_str());
638 }
639
640 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
641
642 ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
643 slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
644
645 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
646 std::set<int32_t> newSuppressedIds;
647 for (size_t i = 0; i < args.pointerCount; i++) {
648 const int32_t pointerId = args.pointerProperties[i].id;
649 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
650 if (!slot) {
651 slot = mSlotState.getSlotForPointerId(pointerId);
652 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
653 }
654 if (slotsToSuppress.test(*slot)) {
655 newSuppressedIds.insert(pointerId);
656 }
657 }
658 return newSuppressedIds;
659}
660
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700661std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
662 if (mPalmDetectionFilter == nullptr) {
663 return {args};
664 }
665 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
666 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
667 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
668 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
669 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
670 args.action == AMOTION_EVENT_ACTION_SCROLL;
671 if (skipThisEvent) {
672 // Lets not process hover events, button events, or scroll for now.
673 return {args};
674 }
675 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
676 mSuppressedPointerIds.clear();
677 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700678
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700679 std::set<int32_t> oldSuppressedIds;
680 std::swap(oldSuppressedIds, mSuppressedPointerIds);
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000681 mSuppressedPointerIds = detectPalmPointers(args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700682
683 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
684 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
685 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
686 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
687 }
688
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700689 // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
690 // subset of oldSuppressedIds.
691 if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
692 mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
693 ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
694 dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
695 args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700696 }
697
698 return argsWithoutUnwantedPointers;
699}
700
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000701const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700702 return mDeviceInfo;
703}
704
705std::string PalmRejector::dump() const {
706 std::string out;
707 out += "mDeviceInfo:\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700708 std::stringstream deviceInfo;
709 deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
710 << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
711 out += addLinePrefix(deviceInfo.str(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700712 out += "mSlotState:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700713 out += addLinePrefix(mSlotState.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700714 out += "mSuppressedPointerIds: ";
715 out += dumpSet(mSuppressedPointerIds) + "\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700716 std::stringstream state;
717 state << *mSharedPalmState;
718 out += "mSharedPalmState: " + state.str() + "\n";
719 std::stringstream filter;
720 filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
721 out += "mPalmDetectionFilter:\n";
722 out += addLinePrefix(filter.str(), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700723 return out;
724}
725
726} // namespace android