blob: ec41025e9d2636966394447c798d01a778d96721 [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 Vishniakou65735832022-08-09 19:18:37 +000080 return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070081}
82
83static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
Siarhei Vishniakou229a8802022-07-28 21:58:56 +000084 return ::base::TimeTicks::UnixEpoch() + ::base::TimeDelta::FromNanosecondsD(eventTime);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070085}
86
87/**
88 * Return true if palm rejection is enabled via the server configurable flags. Return false
89 * otherwise.
90 */
91static bool isPalmRejectionEnabled() {
92 std::string value = toLower(
93 server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
Josep del Rio4b207792022-07-29 16:39:03 +000094 PALM_REJECTION_ENABLED, "0"));
95 if (value == "1") {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -070096 return true;
97 }
98 return false;
99}
100
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000101static int getLinuxToolCode(int toolType) {
102 if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
103 return BTN_TOOL_PEN;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700104 }
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000105 if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
106 return BTN_TOOL_FINGER;
107 }
108 ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
109 return BTN_TOOL_FINGER;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700110}
111
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700112static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
113 for (size_t i = 0; i < args.pointerCount; i++) {
114 if (pointerId == args.pointerProperties[i].id) {
115 return AMOTION_EVENT_ACTION_POINTER_UP |
116 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
117 }
118 }
119 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
120}
121
122/**
123 * Find the action for individual pointer at the given pointer index.
124 * This is always equal to MotionEvent::getActionMasked, except for
125 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
126 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
127 */
128static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
129 const int32_t actionMasked = MotionEvent::getActionMasked(action);
130 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
131 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
132 return actionMasked;
133 }
134 // This is a POINTER_DOWN or POINTER_UP event
135 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
136 if (pointerIndex == actionIndex) {
137 return actionMasked;
138 }
139 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
140 // pointers
141 return AMOTION_EVENT_ACTION_MOVE;
142}
143
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700144NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
145 const std::set<int32_t>& pointerIds) {
146 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
147 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
148 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
149 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
150
151 NotifyMotionArgs newArgs{args};
152 newArgs.pointerCount = 0;
153 int32_t newActionIndex = 0;
154 for (uint32_t i = 0; i < args.pointerCount; i++) {
155 const int32_t pointerId = args.pointerProperties[i].id;
156 if (pointerIds.find(pointerId) != pointerIds.end()) {
157 // skip this pointer
158 if (isPointerUpOrDownAction && i == actionIndex) {
159 // The active pointer is being removed, so the action is no longer valid.
160 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
161 // action later to a proper value.
162 newArgs.action = ACTION_UNKNOWN;
163 }
164 continue;
165 }
166 newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
167 newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
168 if (i == actionIndex) {
169 newActionIndex = newArgs.pointerCount;
170 }
171 newArgs.pointerCount++;
172 }
173 // Update POINTER_DOWN or POINTER_UP actions
174 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
175 newArgs.action =
176 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
177 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
178 if (newArgs.pointerCount == 1) {
179 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
180 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
181 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
182 newArgs.action = AMOTION_EVENT_ACTION_UP;
183 }
184 }
185 }
186 return newArgs;
187}
188
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000189/**
190 * Remove stylus pointers from the provided NotifyMotionArgs.
191 *
192 * Return NotifyMotionArgs where the stylus pointers have been removed.
193 * If this results in removal of the active pointer, then return nullopt.
194 */
195static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
196 std::set<int32_t> stylusPointerIds;
197 for (uint32_t i = 0; i < args.pointerCount; i++) {
198 if (args.pointerProperties[i].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
199 stylusPointerIds.insert(args.pointerProperties[i].id);
200 }
201 }
202 NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
203 if (withoutStylusPointers.pointerCount == 0 || withoutStylusPointers.action == ACTION_UNKNOWN) {
204 return std::nullopt;
205 }
206 return withoutStylusPointers;
207}
208
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700209std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
210 const InputDeviceInfo& deviceInfo) {
211 if (!isFromTouchscreen(deviceInfo.getSources())) {
212 return std::nullopt;
213 }
214 AndroidPalmFilterDeviceInfo out;
215 const InputDeviceInfo::MotionRange* axisX =
216 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
217 if (axisX != nullptr) {
218 out.max_x = axisX->max;
219 out.x_res = axisX->resolution;
220 } else {
221 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
222 deviceInfo.getDisplayName().c_str());
223 return std::nullopt;
224 }
225 const InputDeviceInfo::MotionRange* axisY =
226 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
227 if (axisY != nullptr) {
228 out.max_y = axisY->max;
229 out.y_res = axisY->resolution;
230 } else {
231 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
232 deviceInfo.getDisplayName().c_str());
233 return std::nullopt;
234 }
235 const InputDeviceInfo::MotionRange* axisMajor =
236 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
237 if (axisMajor != nullptr) {
238 out.major_radius_res = axisMajor->resolution;
239 out.touch_major_res = axisMajor->resolution;
240 } else {
241 return std::nullopt;
242 }
243 const InputDeviceInfo::MotionRange* axisMinor =
244 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
245 if (axisMinor != nullptr) {
246 out.minor_radius_res = axisMinor->resolution;
247 out.touch_minor_res = axisMinor->resolution;
248 out.minor_radius_supported = true;
249 } else {
250 out.minor_radius_supported = false;
251 }
252
253 return out;
254}
255
256/**
257 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
258 * that have already been canceled.
259 * The flow of the function is as follows:
260 * 1. Remove all already canceled pointers
261 * 2. Cancel all newly suppressed pointers
262 * 3. Decide what to do with the current event : keep it, or drop it
263 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
264 */
265std::vector<NotifyMotionArgs> cancelSuppressedPointers(
266 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
267 const std::set<int32_t>& newSuppressedPointerIds) {
268 LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
269
270 // First, let's remove the old suppressed pointers. They've already been canceled previously.
271 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
272
273 // Cancel any newly suppressed pointers.
274 std::vector<NotifyMotionArgs> out;
275 const int32_t activePointerId =
276 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
277 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
278 // We will iteratively remove pointers from 'removedArgs'.
279 NotifyMotionArgs removedArgs{oldArgs};
280 for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
281 const int32_t pointerId = oldArgs.pointerProperties[i].id;
282 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
283 // This is a pointer that should not be canceled. Move on.
284 continue;
285 }
286 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
287 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
288 removedArgs = removePointerIds(removedArgs, {pointerId});
289 continue;
290 }
291
292 if (removedArgs.pointerCount == 1) {
293 // We are about to remove the last pointer, which means there will be no more gesture
294 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
295 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
296 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
297 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
298 return {oldArgs};
299 }
300 // Cancel the current pointer
301 out.push_back(removedArgs);
302 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
303 out.back().action = getActionUpForPointerId(out.back(), pointerId);
304
305 // Remove the newly canceled pointer from the args
306 removedArgs = removePointerIds(removedArgs, {pointerId});
307 }
308
309 // Now 'removedArgs' contains only pointers that are valid.
310 if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
311 return out;
312 }
313 out.push_back(removedArgs);
314 return out;
315}
316
317UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
318 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
319
320UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
321 bool enablePalmRejection)
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700322 : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700323
324void UnwantedInteractionBlocker::notifyConfigurationChanged(
325 const NotifyConfigurationChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700326 mQueuedListener.notifyConfigurationChanged(args);
327 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700328}
329
330void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700331 mQueuedListener.notifyKey(args);
332 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700333}
334
335void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000336 ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700337 { // acquire lock
338 std::scoped_lock lock(mLock);
339 const std::vector<NotifyMotionArgs> processedArgs =
340 mPreferStylusOverTouchBlocker.processMotion(*args);
341 for (const NotifyMotionArgs& loopArgs : processedArgs) {
342 notifyMotionLocked(&loopArgs);
343 }
344 } // release lock
345
346 // Call out to the next stage without holding the lock
347 mQueuedListener.flush();
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800348}
349
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000350void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
351 ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
352 mQueuedListener.notifyMotion(&args);
353}
354
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700355void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700356 auto it = mPalmRejectors.find(args->deviceId);
357 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
358 if (!sendToPalmRejector) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000359 enqueueOutboundMotionLocked(*args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700360 return;
361 }
362
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700363 std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
364 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000365 enqueueOutboundMotionLocked(loopArgs);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700366 }
367}
368
369void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700370 mQueuedListener.notifySwitch(args);
371 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700372}
373
374void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700375 mQueuedListener.notifySensor(args);
376 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700377}
378
379void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700380 mQueuedListener.notifyVibratorState(args);
381 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700382}
383void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700384 { // acquire lock
385 std::scoped_lock lock(mLock);
386 auto it = mPalmRejectors.find(args->deviceId);
387 if (it != mPalmRejectors.end()) {
388 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
389 // Re-create the object instead of resetting it
390 mPalmRejectors.erase(it);
391 mPalmRejectors.emplace(args->deviceId, info);
392 }
393 mQueuedListener.notifyDeviceReset(args);
394 mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
395 } // release lock
396 // Send events to the next stage without holding the lock
397 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700398}
399
400void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
401 const NotifyPointerCaptureChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700402 mQueuedListener.notifyPointerCaptureChanged(args);
403 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700404}
405
406void UnwantedInteractionBlocker::notifyInputDevicesChanged(
407 const std::vector<InputDeviceInfo>& inputDevices) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700408 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700409 if (!mEnablePalmRejection) {
410 // Palm rejection is disabled. Don't create any palm rejector objects.
411 return;
412 }
413
414 // Let's see which of the existing devices didn't change, so that we can keep them
415 // and prevent event stream disruption
416 std::set<int32_t /*deviceId*/> devicesToKeep;
417 for (const InputDeviceInfo& device : inputDevices) {
418 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
419 if (!info) {
420 continue;
421 }
422
423 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
424 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
425 // Re-create the PalmRejector because the device info has changed.
426 mPalmRejectors.erase(it);
427 mPalmRejectors.emplace(device.getId(), *info);
428 }
429 devicesToKeep.insert(device.getId());
430 }
431 // Delete all devices that we don't need to keep
432 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
433 auto const& [deviceId, _] = item;
434 return devicesToKeep.find(deviceId) == devicesToKeep.end();
435 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800436 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700437}
438
439void UnwantedInteractionBlocker::dump(std::string& dump) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700440 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700441 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800442 dump += " mPreferStylusOverTouchBlocker:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700443 dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700444 dump += StringPrintf(" mEnablePalmRejection: %s\n",
445 std::to_string(mEnablePalmRejection).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700446 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700447 std::to_string(isPalmRejectionEnabled()).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700448 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
449 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
450 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700451 dump += addLinePrefix(palmRejector.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700452 }
453}
454
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700455void UnwantedInteractionBlocker::monitor() {
456 std::scoped_lock lock(mLock);
457}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700458
459UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
460
461void SlotState::update(const NotifyMotionArgs& args) {
462 for (size_t i = 0; i < args.pointerCount; i++) {
463 const int32_t pointerId = args.pointerProperties[i].id;
464 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
465 processPointerId(pointerId, resolvedAction);
466 }
467}
468
469size_t SlotState::findUnusedSlot() const {
470 size_t unusedSlot = 0;
471 // Since the collection is ordered, we can rely on the in-order traversal
472 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
473 if (unusedSlot != slot) {
474 break;
475 }
476 unusedSlot++;
477 }
478 return unusedSlot;
479}
480
481void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
482 switch (MotionEvent::getActionMasked(actionMasked)) {
483 case AMOTION_EVENT_ACTION_DOWN:
484 case AMOTION_EVENT_ACTION_POINTER_DOWN:
485 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
486 // New pointer going down
487 size_t newSlot = findUnusedSlot();
488 mPointerIdsBySlot[newSlot] = pointerId;
489 mSlotsByPointerId[pointerId] = newSlot;
490 return;
491 }
492 case AMOTION_EVENT_ACTION_MOVE:
493 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
494 return;
495 }
496 case AMOTION_EVENT_ACTION_CANCEL:
497 case AMOTION_EVENT_ACTION_POINTER_UP:
498 case AMOTION_EVENT_ACTION_UP:
499 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
500 auto it = mSlotsByPointerId.find(pointerId);
501 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
502 size_t slot = it->second;
503 // Erase this pointer from both collections
504 mPointerIdsBySlot.erase(slot);
505 mSlotsByPointerId.erase(pointerId);
506 return;
507 }
508 }
509 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
510 return;
511}
512
513std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
514 auto it = mSlotsByPointerId.find(pointerId);
515 if (it == mSlotsByPointerId.end()) {
516 return std::nullopt;
517 }
518 return it->second;
519}
520
521std::string SlotState::dump() const {
522 std::string out = "mSlotsByPointerId:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700523 out += addLinePrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700524 out += "mPointerIdsBySlot:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700525 out += addLinePrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700526 return out;
527}
528
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700529class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
530public:
531 AndroidPalmRejectionModel()
532 : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700533 std::vector<float>()) {
Siarhei Vishniakou5d673462022-07-08 10:47:57 -0700534 config_.resample_period = ::ui::kResamplePeriod;
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700535 }
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700536};
537
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700538PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
539 std::unique_ptr<::ui::PalmDetectionFilter> filter)
540 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
541 mDeviceInfo(info),
542 mPalmDetectionFilter(std::move(filter)) {
543 if (mPalmDetectionFilter != nullptr) {
544 // This path is used for testing. Non-testing invocations should let this constructor
545 // create a real PalmDetectionFilter
546 return;
547 }
548 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700549 std::make_unique<AndroidPalmRejectionModel>();
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700550 mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
551 mSharedPalmState.get());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700552}
553
554std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
555 const AndroidPalmFilterDeviceInfo& deviceInfo,
556 const SlotState& oldSlotState,
557 const SlotState& newSlotState) {
558 std::vector<::ui::InProgressTouchEvdev> touches;
559
560 for (size_t i = 0; i < args.pointerCount; i++) {
561 const int32_t pointerId = args.pointerProperties[i].id;
562 touches.emplace_back(::ui::InProgressTouchEvdev());
563 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
564 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000565 // The field 'tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700566
567 // Whether there is new information for the touch.
568 touches.back().altered = true;
569
570 // Whether the touch was cancelled. Touch events should be ignored till a
571 // new touch is initiated.
572 touches.back().was_cancelled = false;
573
574 // Whether the touch is going to be canceled.
575 touches.back().cancelled = false;
576
577 // Whether the touch is delayed at first appearance. Will not be reported yet.
578 touches.back().delayed = false;
579
580 // Whether the touch was delayed before.
581 touches.back().was_delayed = false;
582
583 // Whether the touch is held until end or no longer held.
584 touches.back().held = false;
585
586 // Whether this touch was held before being sent.
587 touches.back().was_held = false;
588
589 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
590 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
591 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
592 touches.back().was_touching = !isDown;
593
594 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
595 resolvedAction == AMOTION_EVENT_ACTION_UP ||
596 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
597
598 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
599 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
600
601 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
602 if (!slot) {
603 slot = oldSlotState.getSlotForPointerId(pointerId);
604 }
605 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
606 touches.back().slot = *slot;
607 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
608 touches.back().touching = !isUpOrCancel;
609
610 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
611 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000612 touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700613 // The field 'orientation' is not used for palm rejection
614 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000615 // The field 'reported_tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700616 touches.back().stylus_button = false;
617 }
618 return touches;
619}
620
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000621std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
622 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
623 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
624
625 // Store the slot state before we call getTouches and update it. This way, we can find
626 // the slots that have been removed due to the incoming event.
627 SlotState oldSlotState = mSlotState;
628 mSlotState.update(args);
629
630 std::vector<::ui::InProgressTouchEvdev> touches =
631 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
632 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
633
634 if (DEBUG_MODEL) {
635 std::stringstream touchesStream;
636 for (const ::ui::InProgressTouchEvdev& touch : touches) {
637 touchesStream << touch.tracking_id << " : " << touch << "\n";
638 }
639 ALOGD("Filter: touches = %s", touchesStream.str().c_str());
640 }
641
642 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
643
644 ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
645 slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
646
647 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
648 std::set<int32_t> newSuppressedIds;
649 for (size_t i = 0; i < args.pointerCount; i++) {
650 const int32_t pointerId = args.pointerProperties[i].id;
651 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
652 if (!slot) {
653 slot = mSlotState.getSlotForPointerId(pointerId);
654 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
655 }
656 if (slotsToSuppress.test(*slot)) {
657 newSuppressedIds.insert(pointerId);
658 }
659 }
660 return newSuppressedIds;
661}
662
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700663std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
664 if (mPalmDetectionFilter == nullptr) {
665 return {args};
666 }
667 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
668 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
669 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
670 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
671 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
672 args.action == AMOTION_EVENT_ACTION_SCROLL;
673 if (skipThisEvent) {
674 // Lets not process hover events, button events, or scroll for now.
675 return {args};
676 }
677 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
678 mSuppressedPointerIds.clear();
679 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700680
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700681 std::set<int32_t> oldSuppressedIds;
682 std::swap(oldSuppressedIds, mSuppressedPointerIds);
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000683
684 std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
685 if (touchOnlyArgs) {
686 mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
687 } else {
688 // This is a stylus-only event.
689 // We can skip this event and just keep the suppressed pointer ids the same as before.
690 mSuppressedPointerIds = oldSuppressedIds;
691 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700692
693 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
694 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
695 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
696 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
697 }
698
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700699 // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
700 // subset of oldSuppressedIds.
701 if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
702 mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
703 ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
704 dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
705 args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700706 }
707
708 return argsWithoutUnwantedPointers;
709}
710
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000711const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700712 return mDeviceInfo;
713}
714
715std::string PalmRejector::dump() const {
716 std::string out;
717 out += "mDeviceInfo:\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700718 std::stringstream deviceInfo;
719 deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
720 << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
721 out += addLinePrefix(deviceInfo.str(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700722 out += "mSlotState:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700723 out += addLinePrefix(mSlotState.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700724 out += "mSuppressedPointerIds: ";
725 out += dumpSet(mSuppressedPointerIds) + "\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700726 std::stringstream state;
727 state << *mSharedPalmState;
728 out += "mSharedPalmState: " + state.str() + "\n";
729 std::stringstream filter;
730 filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
731 out += "mPalmDetectionFilter:\n";
732 out += addLinePrefix(filter.str(), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700733 return out;
734}
735
736} // namespace android