blob: c170b81475b9e8d01b8731d1bfab1875baebaf7f [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) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000102 switch (toolType) {
103 case AMOTION_EVENT_TOOL_TYPE_STYLUS:
104 return BTN_TOOL_PEN;
105 case AMOTION_EVENT_TOOL_TYPE_ERASER:
106 return BTN_TOOL_RUBBER;
107 case AMOTION_EVENT_TOOL_TYPE_FINGER:
108 return BTN_TOOL_FINGER;
109 default:
110 ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
111 return BTN_TOOL_FINGER;
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700112 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700113}
114
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700115static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
116 for (size_t i = 0; i < args.pointerCount; i++) {
117 if (pointerId == args.pointerProperties[i].id) {
118 return AMOTION_EVENT_ACTION_POINTER_UP |
119 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
120 }
121 }
122 LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
123}
124
125/**
126 * Find the action for individual pointer at the given pointer index.
127 * This is always equal to MotionEvent::getActionMasked, except for
128 * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
129 * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
130 */
131static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
132 const int32_t actionMasked = MotionEvent::getActionMasked(action);
133 if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
134 actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
135 return actionMasked;
136 }
137 // This is a POINTER_DOWN or POINTER_UP event
138 const uint8_t actionIndex = MotionEvent::getActionIndex(action);
139 if (pointerIndex == actionIndex) {
140 return actionMasked;
141 }
142 // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
143 // pointers
144 return AMOTION_EVENT_ACTION_MOVE;
145}
146
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700147NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
148 const std::set<int32_t>& pointerIds) {
149 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
150 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
151 const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
152 actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
153
154 NotifyMotionArgs newArgs{args};
155 newArgs.pointerCount = 0;
156 int32_t newActionIndex = 0;
157 for (uint32_t i = 0; i < args.pointerCount; i++) {
158 const int32_t pointerId = args.pointerProperties[i].id;
159 if (pointerIds.find(pointerId) != pointerIds.end()) {
160 // skip this pointer
161 if (isPointerUpOrDownAction && i == actionIndex) {
162 // The active pointer is being removed, so the action is no longer valid.
163 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
164 // action later to a proper value.
165 newArgs.action = ACTION_UNKNOWN;
166 }
167 continue;
168 }
169 newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
170 newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
171 if (i == actionIndex) {
172 newActionIndex = newArgs.pointerCount;
173 }
174 newArgs.pointerCount++;
175 }
176 // Update POINTER_DOWN or POINTER_UP actions
177 if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
178 newArgs.action =
179 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
180 // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
181 if (newArgs.pointerCount == 1) {
182 if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
183 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
184 } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
185 newArgs.action = AMOTION_EVENT_ACTION_UP;
186 }
187 }
188 }
189 return newArgs;
190}
191
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000192/**
193 * Remove stylus pointers from the provided NotifyMotionArgs.
194 *
195 * Return NotifyMotionArgs where the stylus pointers have been removed.
196 * If this results in removal of the active pointer, then return nullopt.
197 */
198static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
199 std::set<int32_t> stylusPointerIds;
200 for (uint32_t i = 0; i < args.pointerCount; i++) {
Prabir Pradhane5626962022-10-27 20:30:53 +0000201 if (isStylusToolType(args.pointerProperties[i].toolType)) {
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000202 stylusPointerIds.insert(args.pointerProperties[i].id);
203 }
204 }
205 NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
206 if (withoutStylusPointers.pointerCount == 0 || withoutStylusPointers.action == ACTION_UNKNOWN) {
207 return std::nullopt;
208 }
209 return withoutStylusPointers;
210}
211
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700212std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
213 const InputDeviceInfo& deviceInfo) {
214 if (!isFromTouchscreen(deviceInfo.getSources())) {
215 return std::nullopt;
216 }
217 AndroidPalmFilterDeviceInfo out;
218 const InputDeviceInfo::MotionRange* axisX =
219 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
220 if (axisX != nullptr) {
221 out.max_x = axisX->max;
222 out.x_res = axisX->resolution;
223 } else {
224 ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
225 deviceInfo.getDisplayName().c_str());
226 return std::nullopt;
227 }
228 const InputDeviceInfo::MotionRange* axisY =
229 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
230 if (axisY != nullptr) {
231 out.max_y = axisY->max;
232 out.y_res = axisY->resolution;
233 } else {
234 ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
235 deviceInfo.getDisplayName().c_str());
236 return std::nullopt;
237 }
238 const InputDeviceInfo::MotionRange* axisMajor =
239 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
240 if (axisMajor != nullptr) {
241 out.major_radius_res = axisMajor->resolution;
242 out.touch_major_res = axisMajor->resolution;
243 } else {
244 return std::nullopt;
245 }
246 const InputDeviceInfo::MotionRange* axisMinor =
247 deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
248 if (axisMinor != nullptr) {
249 out.minor_radius_res = axisMinor->resolution;
250 out.touch_minor_res = axisMinor->resolution;
251 out.minor_radius_supported = true;
252 } else {
253 out.minor_radius_supported = false;
254 }
255
256 return out;
257}
258
259/**
260 * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
261 * that have already been canceled.
262 * The flow of the function is as follows:
263 * 1. Remove all already canceled pointers
264 * 2. Cancel all newly suppressed pointers
265 * 3. Decide what to do with the current event : keep it, or drop it
266 * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
267 */
268std::vector<NotifyMotionArgs> cancelSuppressedPointers(
269 const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
270 const std::set<int32_t>& newSuppressedPointerIds) {
271 LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
272
273 // First, let's remove the old suppressed pointers. They've already been canceled previously.
274 NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
275
276 // Cancel any newly suppressed pointers.
277 std::vector<NotifyMotionArgs> out;
278 const int32_t activePointerId =
279 args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
280 const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
281 // We will iteratively remove pointers from 'removedArgs'.
282 NotifyMotionArgs removedArgs{oldArgs};
283 for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
284 const int32_t pointerId = oldArgs.pointerProperties[i].id;
285 if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
286 // This is a pointer that should not be canceled. Move on.
287 continue;
288 }
289 if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
290 // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
291 removedArgs = removePointerIds(removedArgs, {pointerId});
292 continue;
293 }
294
295 if (removedArgs.pointerCount == 1) {
296 // We are about to remove the last pointer, which means there will be no more gesture
297 // remaining. This is identical to canceling all pointers, so just send a single CANCEL
298 // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
299 oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
300 oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
301 return {oldArgs};
302 }
303 // Cancel the current pointer
304 out.push_back(removedArgs);
305 out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
306 out.back().action = getActionUpForPointerId(out.back(), pointerId);
307
308 // Remove the newly canceled pointer from the args
309 removedArgs = removePointerIds(removedArgs, {pointerId});
310 }
311
312 // Now 'removedArgs' contains only pointers that are valid.
313 if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
314 return out;
315 }
316 out.push_back(removedArgs);
317 return out;
318}
319
320UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
321 : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
322
323UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
324 bool enablePalmRejection)
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700325 : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700326
327void UnwantedInteractionBlocker::notifyConfigurationChanged(
328 const NotifyConfigurationChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700329 mQueuedListener.notifyConfigurationChanged(args);
330 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700331}
332
333void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700334 mQueuedListener.notifyKey(args);
335 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700336}
337
338void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000339 ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700340 { // acquire lock
341 std::scoped_lock lock(mLock);
342 const std::vector<NotifyMotionArgs> processedArgs =
343 mPreferStylusOverTouchBlocker.processMotion(*args);
344 for (const NotifyMotionArgs& loopArgs : processedArgs) {
345 notifyMotionLocked(&loopArgs);
346 }
347 } // release lock
348
349 // Call out to the next stage without holding the lock
350 mQueuedListener.flush();
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800351}
352
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000353void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
354 ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
355 mQueuedListener.notifyMotion(&args);
356}
357
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700358void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700359 auto it = mPalmRejectors.find(args->deviceId);
360 const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
361 if (!sendToPalmRejector) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000362 enqueueOutboundMotionLocked(*args);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700363 return;
364 }
365
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700366 std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
367 for (const NotifyMotionArgs& loopArgs : processedArgs) {
Siarhei Vishniakoud5fe5182022-07-20 23:28:40 +0000368 enqueueOutboundMotionLocked(loopArgs);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700369 }
370}
371
372void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700373 mQueuedListener.notifySwitch(args);
374 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700375}
376
377void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700378 mQueuedListener.notifySensor(args);
379 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700380}
381
382void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700383 mQueuedListener.notifyVibratorState(args);
384 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700385}
386void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700387 { // acquire lock
388 std::scoped_lock lock(mLock);
389 auto it = mPalmRejectors.find(args->deviceId);
390 if (it != mPalmRejectors.end()) {
391 AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
392 // Re-create the object instead of resetting it
393 mPalmRejectors.erase(it);
394 mPalmRejectors.emplace(args->deviceId, info);
395 }
396 mQueuedListener.notifyDeviceReset(args);
397 mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
398 } // release lock
399 // Send events to the next stage without holding the lock
400 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700401}
402
403void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
404 const NotifyPointerCaptureChangedArgs* args) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700405 mQueuedListener.notifyPointerCaptureChanged(args);
406 mQueuedListener.flush();
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700407}
408
409void UnwantedInteractionBlocker::notifyInputDevicesChanged(
410 const std::vector<InputDeviceInfo>& inputDevices) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700411 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700412 if (!mEnablePalmRejection) {
413 // Palm rejection is disabled. Don't create any palm rejector objects.
414 return;
415 }
416
417 // Let's see which of the existing devices didn't change, so that we can keep them
418 // and prevent event stream disruption
419 std::set<int32_t /*deviceId*/> devicesToKeep;
420 for (const InputDeviceInfo& device : inputDevices) {
421 std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
422 if (!info) {
423 continue;
424 }
425
426 auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
427 if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
428 // Re-create the PalmRejector because the device info has changed.
429 mPalmRejectors.erase(it);
430 mPalmRejectors.emplace(device.getId(), *info);
431 }
432 devicesToKeep.insert(device.getId());
433 }
434 // Delete all devices that we don't need to keep
435 std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
436 auto const& [deviceId, _] = item;
437 return devicesToKeep.find(deviceId) == devicesToKeep.end();
438 });
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800439 mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700440}
441
442void UnwantedInteractionBlocker::dump(std::string& dump) {
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700443 std::scoped_lock lock(mLock);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700444 dump += "UnwantedInteractionBlocker:\n";
Siarhei Vishniakou814ace32022-03-04 15:12:16 -0800445 dump += " mPreferStylusOverTouchBlocker:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700446 dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), " ");
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700447 dump += StringPrintf(" mEnablePalmRejection: %s\n",
448 std::to_string(mEnablePalmRejection).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700449 dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n",
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700450 std::to_string(isPalmRejectionEnabled()).c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700451 dump += mPalmRejectors.empty() ? " mPalmRejectors: None\n" : " mPalmRejectors:\n";
452 for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
453 dump += StringPrintf(" deviceId = %" PRId32 ":\n", deviceId);
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700454 dump += addLinePrefix(palmRejector.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700455 }
456}
457
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700458void UnwantedInteractionBlocker::monitor() {
459 std::scoped_lock lock(mLock);
460}
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700461
462UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
463
464void SlotState::update(const NotifyMotionArgs& args) {
465 for (size_t i = 0; i < args.pointerCount; i++) {
466 const int32_t pointerId = args.pointerProperties[i].id;
467 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
468 processPointerId(pointerId, resolvedAction);
469 }
470}
471
472size_t SlotState::findUnusedSlot() const {
473 size_t unusedSlot = 0;
474 // Since the collection is ordered, we can rely on the in-order traversal
475 for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
476 if (unusedSlot != slot) {
477 break;
478 }
479 unusedSlot++;
480 }
481 return unusedSlot;
482}
483
484void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
485 switch (MotionEvent::getActionMasked(actionMasked)) {
486 case AMOTION_EVENT_ACTION_DOWN:
487 case AMOTION_EVENT_ACTION_POINTER_DOWN:
488 case AMOTION_EVENT_ACTION_HOVER_ENTER: {
489 // New pointer going down
490 size_t newSlot = findUnusedSlot();
491 mPointerIdsBySlot[newSlot] = pointerId;
492 mSlotsByPointerId[pointerId] = newSlot;
493 return;
494 }
495 case AMOTION_EVENT_ACTION_MOVE:
496 case AMOTION_EVENT_ACTION_HOVER_MOVE: {
497 return;
498 }
499 case AMOTION_EVENT_ACTION_CANCEL:
500 case AMOTION_EVENT_ACTION_POINTER_UP:
501 case AMOTION_EVENT_ACTION_UP:
502 case AMOTION_EVENT_ACTION_HOVER_EXIT: {
503 auto it = mSlotsByPointerId.find(pointerId);
504 LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
505 size_t slot = it->second;
506 // Erase this pointer from both collections
507 mPointerIdsBySlot.erase(slot);
508 mSlotsByPointerId.erase(pointerId);
509 return;
510 }
511 }
512 LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
513 return;
514}
515
516std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
517 auto it = mSlotsByPointerId.find(pointerId);
518 if (it == mSlotsByPointerId.end()) {
519 return std::nullopt;
520 }
521 return it->second;
522}
523
524std::string SlotState::dump() const {
525 std::string out = "mSlotsByPointerId:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700526 out += addLinePrefix(dumpMap(mSlotsByPointerId), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700527 out += "mPointerIdsBySlot:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700528 out += addLinePrefix(dumpMap(mPointerIdsBySlot), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700529 return out;
530}
531
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700532class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
533public:
534 AndroidPalmRejectionModel()
535 : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700536 std::vector<float>()) {
Siarhei Vishniakou5d673462022-07-08 10:47:57 -0700537 config_.resample_period = ::ui::kResamplePeriod;
Siarhei Vishniakou127b45d2022-06-13 13:56:56 -0700538 }
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700539};
540
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700541PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
542 std::unique_ptr<::ui::PalmDetectionFilter> filter)
543 : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
544 mDeviceInfo(info),
545 mPalmDetectionFilter(std::move(filter)) {
546 if (mPalmDetectionFilter != nullptr) {
547 // This path is used for testing. Non-testing invocations should let this constructor
548 // create a real PalmDetectionFilter
549 return;
550 }
551 std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
Siarhei Vishniakou4aeef8c2022-06-13 11:19:34 -0700552 std::make_unique<AndroidPalmRejectionModel>();
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700553 mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
554 mSharedPalmState.get());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700555}
556
557std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
558 const AndroidPalmFilterDeviceInfo& deviceInfo,
559 const SlotState& oldSlotState,
560 const SlotState& newSlotState) {
561 std::vector<::ui::InProgressTouchEvdev> touches;
562
563 for (size_t i = 0; i < args.pointerCount; i++) {
564 const int32_t pointerId = args.pointerProperties[i].id;
565 touches.emplace_back(::ui::InProgressTouchEvdev());
566 touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
567 touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000568 // The field 'tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700569
570 // Whether there is new information for the touch.
571 touches.back().altered = true;
572
573 // Whether the touch was cancelled. Touch events should be ignored till a
574 // new touch is initiated.
575 touches.back().was_cancelled = false;
576
577 // Whether the touch is going to be canceled.
578 touches.back().cancelled = false;
579
580 // Whether the touch is delayed at first appearance. Will not be reported yet.
581 touches.back().delayed = false;
582
583 // Whether the touch was delayed before.
584 touches.back().was_delayed = false;
585
586 // Whether the touch is held until end or no longer held.
587 touches.back().held = false;
588
589 // Whether this touch was held before being sent.
590 touches.back().was_held = false;
591
592 const int32_t resolvedAction = resolveActionForPointer(i, args.action);
593 const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
594 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
595 touches.back().was_touching = !isDown;
596
597 const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
598 resolvedAction == AMOTION_EVENT_ACTION_UP ||
599 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
600
601 touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
602 touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
603
604 std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
605 if (!slot) {
606 slot = oldSlotState.getSlotForPointerId(pointerId);
607 }
608 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
609 touches.back().slot = *slot;
610 touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
611 touches.back().touching = !isUpOrCancel;
612
613 // The fields 'radius_x' and 'radius_x' are not used for palm rejection
614 touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000615 touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700616 // The field 'orientation' is not used for palm rejection
617 // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000618 // The field 'reported_tool_type' is not used for palm rejection
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700619 touches.back().stylus_button = false;
620 }
621 return touches;
622}
623
Siarhei Vishniakoue491fb52022-08-11 01:51:24 +0000624std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
625 std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
626 std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
627
628 // Store the slot state before we call getTouches and update it. This way, we can find
629 // the slots that have been removed due to the incoming event.
630 SlotState oldSlotState = mSlotState;
631 mSlotState.update(args);
632
633 std::vector<::ui::InProgressTouchEvdev> touches =
634 getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
635 ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
636
637 if (DEBUG_MODEL) {
638 std::stringstream touchesStream;
639 for (const ::ui::InProgressTouchEvdev& touch : touches) {
640 touchesStream << touch.tracking_id << " : " << touch << "\n";
641 }
642 ALOGD("Filter: touches = %s", touchesStream.str().c_str());
643 }
644
645 mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
646
647 ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
648 slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
649
650 // Now that we know which slots should be suppressed, let's convert those to pointer id's.
651 std::set<int32_t> newSuppressedIds;
652 for (size_t i = 0; i < args.pointerCount; i++) {
653 const int32_t pointerId = args.pointerProperties[i].id;
654 std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
655 if (!slot) {
656 slot = mSlotState.getSlotForPointerId(pointerId);
657 LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
658 }
659 if (slotsToSuppress.test(*slot)) {
660 newSuppressedIds.insert(pointerId);
661 }
662 }
663 return newSuppressedIds;
664}
665
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700666std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
667 if (mPalmDetectionFilter == nullptr) {
668 return {args};
669 }
670 const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
671 args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
672 args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
673 args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
674 args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
675 args.action == AMOTION_EVENT_ACTION_SCROLL;
676 if (skipThisEvent) {
677 // Lets not process hover events, button events, or scroll for now.
678 return {args};
679 }
680 if (args.action == AMOTION_EVENT_ACTION_DOWN) {
681 mSuppressedPointerIds.clear();
682 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700683
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700684 std::set<int32_t> oldSuppressedIds;
685 std::swap(oldSuppressedIds, mSuppressedPointerIds);
Siarhei Vishniakou65735832022-08-09 19:18:37 +0000686
687 std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
688 if (touchOnlyArgs) {
689 mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
690 } else {
691 // This is a stylus-only event.
692 // We can skip this event and just keep the suppressed pointer ids the same as before.
693 mSuppressedPointerIds = oldSuppressedIds;
694 }
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700695
696 std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
697 cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
698 for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
699 LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
700 }
701
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700702 // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
703 // subset of oldSuppressedIds.
704 if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
705 mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
706 ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
707 dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
708 args.dump().c_str());
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700709 }
710
711 return argsWithoutUnwantedPointers;
712}
713
Siarhei Vishniakou88151b82022-08-11 00:53:38 +0000714const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700715 return mDeviceInfo;
716}
717
718std::string PalmRejector::dump() const {
719 std::string out;
720 out += "mDeviceInfo:\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700721 std::stringstream deviceInfo;
722 deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
723 << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
724 out += addLinePrefix(deviceInfo.str(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700725 out += "mSlotState:\n";
Siarhei Vishniakoua91d8572022-05-17 05:03:42 -0700726 out += addLinePrefix(mSlotState.dump(), " ");
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700727 out += "mSuppressedPointerIds: ";
728 out += dumpSet(mSuppressedPointerIds) + "\n";
Siarhei Vishniakou15a5c5a2022-07-06 15:42:57 -0700729 std::stringstream state;
730 state << *mSharedPalmState;
731 out += "mSharedPalmState: " + state.str() + "\n";
732 std::stringstream filter;
733 filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
734 out += "mPalmDetectionFilter:\n";
735 out += addLinePrefix(filter.str(), " ") + "\n";
Siarhei Vishniakouba0a8752021-09-14 14:43:25 -0700736 return out;
737}
738
739} // namespace android