• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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>
21 #include <ftl/enum.h>
22 #include <input/PrintTools.h>
23 #include <inttypes.h>
24 #include <linux/input-event-codes.h>
25 #include <linux/input.h>
26 #include <server_configurable_flags/get_flags.h>
27 
28 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
29 #include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
30 
31 using android::base::StringPrintf;
32 
33 /**
34  * This type is declared here to ensure consistency between the instantiated type (used in the
35  * constructor via std::make_unique) and the cast-to type (used in PalmRejector::dump() with
36  * static_cast). Due to the lack of rtti support, dynamic_cast is not available, so this can't be
37  * checked at runtime to avoid undefined behaviour.
38  */
39 using PalmFilterImplementation = ::ui::NeuralStylusPalmDetectionFilter;
40 
41 namespace android {
42 
43 /**
44  * Log detailed debug messages about each inbound motion event notification to the blocker.
45  * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerInboundMotion DEBUG"
46  * (requires restart)
47  */
48 const bool DEBUG_INBOUND_MOTION =
49         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundMotion", ANDROID_LOG_INFO);
50 
51 /**
52  * Log detailed debug messages about each outbound motion event processed by the blocker.
53  * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerOutboundMotion DEBUG"
54  * (requires restart)
55  */
56 const bool DEBUG_OUTBOUND_MOTION =
57         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundMotion", ANDROID_LOG_INFO);
58 
59 /**
60  * Log the data sent to the model and received back from the model.
61  * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerModel DEBUG"
62  * (requires restart)
63  */
64 const bool DEBUG_MODEL =
65         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
66 
67 // Category (=namespace) name for the input settings that are applied at boot time
68 static const char* INPUT_NATIVE_BOOT = "input_native_boot";
69 /**
70  * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
71  * 'true' (not case sensitive) or '1'. To disable, specify any other value.
72  */
73 static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
74 
toLower(std::string s)75 static std::string toLower(std::string s) {
76     std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
77     return s;
78 }
79 
isFromTouchscreen(int32_t source)80 static bool isFromTouchscreen(int32_t source) {
81     return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
82 }
83 
toChromeTimestamp(nsecs_t eventTime)84 static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
85     return ::base::TimeTicks::UnixEpoch() + ::base::TimeDelta::FromNanosecondsD(eventTime);
86 }
87 
88 /**
89  * Return true if palm rejection is enabled via the server configurable flags. Return false
90  * otherwise.
91  */
isPalmRejectionEnabled()92 static bool isPalmRejectionEnabled() {
93     std::string value = toLower(
94             server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
95                                                                  PALM_REJECTION_ENABLED, "0"));
96     if (value == "1") {
97         return true;
98     }
99     return false;
100 }
101 
getLinuxToolCode(ToolType toolType)102 static int getLinuxToolCode(ToolType toolType) {
103     switch (toolType) {
104         case ToolType::STYLUS:
105             return BTN_TOOL_PEN;
106         case ToolType::ERASER:
107             return BTN_TOOL_RUBBER;
108         case ToolType::FINGER:
109             return BTN_TOOL_FINGER;
110         case ToolType::UNKNOWN:
111         case ToolType::MOUSE:
112         case ToolType::PALM:
113             break;
114     }
115     ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str());
116     return BTN_TOOL_FINGER;
117 }
118 
getActionUpForPointerId(const NotifyMotionArgs & args,int32_t pointerId)119 static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
120     for (size_t i = 0; i < args.getPointerCount(); i++) {
121         if (pointerId == args.pointerProperties[i].id) {
122             return AMOTION_EVENT_ACTION_POINTER_UP |
123                     (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
124         }
125     }
126     LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
127 }
128 
129 /**
130  * Find the action for individual pointer at the given pointer index.
131  * This is always equal to MotionEvent::getActionMasked, except for
132  * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
133  * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
134  */
resolveActionForPointer(uint8_t pointerIndex,int32_t action)135 static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
136     const int32_t actionMasked = MotionEvent::getActionMasked(action);
137     if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
138         actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
139         return actionMasked;
140     }
141     // This is a POINTER_DOWN or POINTER_UP event
142     const uint8_t actionIndex = MotionEvent::getActionIndex(action);
143     if (pointerIndex == actionIndex) {
144         return actionMasked;
145     }
146     // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
147     // pointers
148     return AMOTION_EVENT_ACTION_MOVE;
149 }
150 
removePointerIds(const NotifyMotionArgs & args,const std::set<int32_t> & pointerIds)151 NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
152                                   const std::set<int32_t>& pointerIds) {
153     const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
154     const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
155     const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
156             actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
157 
158     NotifyMotionArgs newArgs{args};
159     newArgs.pointerProperties.clear();
160     newArgs.pointerCoords.clear();
161     int32_t newActionIndex = 0;
162     for (uint32_t i = 0; i < args.getPointerCount(); i++) {
163         const int32_t pointerId = args.pointerProperties[i].id;
164         if (pointerIds.find(pointerId) != pointerIds.end()) {
165             // skip this pointer
166             if (isPointerUpOrDownAction && i == actionIndex) {
167                 // The active pointer is being removed, so the action is no longer valid.
168                 // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
169                 // action later to a proper value.
170                 newArgs.action = ACTION_UNKNOWN;
171             }
172             continue;
173         }
174         newArgs.pointerProperties.push_back(args.pointerProperties[i]);
175         newArgs.pointerCoords.push_back(args.pointerCoords[i]);
176         if (i == actionIndex) {
177             newActionIndex = newArgs.getPointerCount() - 1;
178         }
179     }
180     // Update POINTER_DOWN or POINTER_UP actions
181     if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
182         newArgs.action =
183                 actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
184         // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
185         if (newArgs.getPointerCount() == 1) {
186             if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
187                 newArgs.action = AMOTION_EVENT_ACTION_DOWN;
188             } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
189                 newArgs.action = AMOTION_EVENT_ACTION_UP;
190             }
191         }
192     }
193     return newArgs;
194 }
195 
196 /**
197  * Remove stylus pointers from the provided NotifyMotionArgs.
198  *
199  * Return NotifyMotionArgs where the stylus pointers have been removed.
200  * If this results in removal of the active pointer, then return nullopt.
201  */
removeStylusPointerIds(const NotifyMotionArgs & args)202 static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
203     std::set<int32_t> stylusPointerIds;
204     for (uint32_t i = 0; i < args.getPointerCount(); i++) {
205         if (isStylusToolType(args.pointerProperties[i].toolType)) {
206             stylusPointerIds.insert(args.pointerProperties[i].id);
207         }
208     }
209     NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
210     if (withoutStylusPointers.getPointerCount() == 0 ||
211         withoutStylusPointers.action == ACTION_UNKNOWN) {
212         return std::nullopt;
213     }
214     return withoutStylusPointers;
215 }
216 
createPalmFilterDeviceInfo(const InputDeviceInfo & deviceInfo)217 std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
218         const InputDeviceInfo& deviceInfo) {
219     if (!isFromTouchscreen(deviceInfo.getSources())) {
220         return std::nullopt;
221     }
222     AndroidPalmFilterDeviceInfo out;
223     const InputDeviceInfo::MotionRange* axisX =
224             deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
225     if (axisX != nullptr) {
226         out.max_x = axisX->max;
227         out.x_res = axisX->resolution;
228     } else {
229         ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
230               deviceInfo.getDisplayName().c_str());
231         return std::nullopt;
232     }
233     const InputDeviceInfo::MotionRange* axisY =
234             deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
235     if (axisY != nullptr) {
236         out.max_y = axisY->max;
237         out.y_res = axisY->resolution;
238     } else {
239         ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
240               deviceInfo.getDisplayName().c_str());
241         return std::nullopt;
242     }
243     const InputDeviceInfo::MotionRange* axisMajor =
244             deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
245     if (axisMajor != nullptr) {
246         out.major_radius_res = axisMajor->resolution;
247         out.touch_major_res = axisMajor->resolution;
248     } else {
249         return std::nullopt;
250     }
251     const InputDeviceInfo::MotionRange* axisMinor =
252             deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
253     if (axisMinor != nullptr) {
254         out.minor_radius_res = axisMinor->resolution;
255         out.touch_minor_res = axisMinor->resolution;
256         out.minor_radius_supported = true;
257     } else {
258         out.minor_radius_supported = false;
259     }
260 
261     return out;
262 }
263 
264 /**
265  * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
266  * that have already been canceled.
267  * The flow of the function is as follows:
268  * 1. Remove all already canceled pointers
269  * 2. Cancel all newly suppressed pointers
270  * 3. Decide what to do with the current event : keep it, or drop it
271  * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
272  */
cancelSuppressedPointers(const NotifyMotionArgs & args,const std::set<int32_t> & oldSuppressedPointerIds,const std::set<int32_t> & newSuppressedPointerIds)273 std::vector<NotifyMotionArgs> cancelSuppressedPointers(
274         const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
275         const std::set<int32_t>& newSuppressedPointerIds) {
276     LOG_ALWAYS_FATAL_IF(args.getPointerCount() == 0, "0 pointers in %s", args.dump().c_str());
277 
278     // First, let's remove the old suppressed pointers. They've already been canceled previously.
279     NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
280 
281     // Cancel any newly suppressed pointers.
282     std::vector<NotifyMotionArgs> out;
283     const int32_t activePointerId =
284             args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
285     const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
286     // We will iteratively remove pointers from 'removedArgs'.
287     NotifyMotionArgs removedArgs{oldArgs};
288     for (uint32_t i = 0; i < oldArgs.getPointerCount(); i++) {
289         const int32_t pointerId = oldArgs.pointerProperties[i].id;
290         if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
291             // This is a pointer that should not be canceled. Move on.
292             continue;
293         }
294         if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
295             // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
296             removedArgs = removePointerIds(removedArgs, {pointerId});
297             continue;
298         }
299 
300         if (removedArgs.getPointerCount() == 1) {
301             // We are about to remove the last pointer, which means there will be no more gesture
302             // remaining. This is identical to canceling all pointers, so just send a single CANCEL
303             // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
304             oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
305             oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
306             return {oldArgs};
307         }
308         // Cancel the current pointer
309         out.push_back(removedArgs);
310         out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
311         out.back().action = getActionUpForPointerId(out.back(), pointerId);
312 
313         // Remove the newly canceled pointer from the args
314         removedArgs = removePointerIds(removedArgs, {pointerId});
315     }
316 
317     // Now 'removedArgs' contains only pointers that are valid.
318     if (removedArgs.getPointerCount() <= 0 || removedArgs.action == ACTION_UNKNOWN) {
319         return out;
320     }
321     out.push_back(removedArgs);
322     return out;
323 }
324 
UnwantedInteractionBlocker(InputListenerInterface & listener)325 UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
326       : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
327 
UnwantedInteractionBlocker(InputListenerInterface & listener,bool enablePalmRejection)328 UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
329                                                        bool enablePalmRejection)
330       : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
331 
notifyConfigurationChanged(const NotifyConfigurationChangedArgs & args)332 void UnwantedInteractionBlocker::notifyConfigurationChanged(
333         const NotifyConfigurationChangedArgs& args) {
334     mQueuedListener.notifyConfigurationChanged(args);
335     mQueuedListener.flush();
336 }
337 
notifyKey(const NotifyKeyArgs & args)338 void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
339     mQueuedListener.notifyKey(args);
340     mQueuedListener.flush();
341 }
342 
notifyMotion(const NotifyMotionArgs & args)343 void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
344     ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
345     { // acquire lock
346         std::scoped_lock lock(mLock);
347         const std::vector<NotifyMotionArgs> processedArgs =
348                 mPreferStylusOverTouchBlocker.processMotion(args);
349         for (const NotifyMotionArgs& loopArgs : processedArgs) {
350             notifyMotionLocked(loopArgs);
351         }
352     } // release lock
353 
354     // Call out to the next stage without holding the lock
355     mQueuedListener.flush();
356 }
357 
enqueueOutboundMotionLocked(const NotifyMotionArgs & args)358 void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
359     ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
360     mQueuedListener.notifyMotion(args);
361 }
362 
notifyMotionLocked(const NotifyMotionArgs & args)363 void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) {
364     auto it = mPalmRejectors.find(args.deviceId);
365     const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source);
366     if (!sendToPalmRejector) {
367         enqueueOutboundMotionLocked(args);
368         return;
369     }
370 
371     std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(args);
372     for (const NotifyMotionArgs& loopArgs : processedArgs) {
373         enqueueOutboundMotionLocked(loopArgs);
374     }
375 }
376 
notifySwitch(const NotifySwitchArgs & args)377 void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) {
378     mQueuedListener.notifySwitch(args);
379     mQueuedListener.flush();
380 }
381 
notifySensor(const NotifySensorArgs & args)382 void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) {
383     mQueuedListener.notifySensor(args);
384     mQueuedListener.flush();
385 }
386 
notifyVibratorState(const NotifyVibratorStateArgs & args)387 void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) {
388     mQueuedListener.notifyVibratorState(args);
389     mQueuedListener.flush();
390 }
notifyDeviceReset(const NotifyDeviceResetArgs & args)391 void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
392     { // acquire lock
393         std::scoped_lock lock(mLock);
394         auto it = mPalmRejectors.find(args.deviceId);
395         if (it != mPalmRejectors.end()) {
396             AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
397             // Re-create the object instead of resetting it
398             mPalmRejectors.erase(it);
399             mPalmRejectors.emplace(args.deviceId, info);
400         }
401         mQueuedListener.notifyDeviceReset(args);
402         mPreferStylusOverTouchBlocker.notifyDeviceReset(args);
403     } // release lock
404     // Send events to the next stage without holding the lock
405     mQueuedListener.flush();
406 }
407 
notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs & args)408 void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
409         const NotifyPointerCaptureChangedArgs& args) {
410     mQueuedListener.notifyPointerCaptureChanged(args);
411     mQueuedListener.flush();
412 }
413 
notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs & args)414 void UnwantedInteractionBlocker::notifyInputDevicesChanged(
415         const NotifyInputDevicesChangedArgs& args) {
416     onInputDevicesChanged(args.inputDeviceInfos);
417     mQueuedListener.notify(args);
418     mQueuedListener.flush();
419 }
420 
onInputDevicesChanged(const std::vector<InputDeviceInfo> & inputDevices)421 void UnwantedInteractionBlocker::onInputDevicesChanged(
422         const std::vector<InputDeviceInfo>& inputDevices) {
423     std::scoped_lock lock(mLock);
424     if (!mEnablePalmRejection) {
425         // Palm rejection is disabled. Don't create any palm rejector objects.
426         return;
427     }
428 
429     // Let's see which of the existing devices didn't change, so that we can keep them
430     // and prevent event stream disruption
431     std::set<int32_t /*deviceId*/> devicesToKeep;
432     for (const InputDeviceInfo& device : inputDevices) {
433         std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
434         if (!info) {
435             continue;
436         }
437 
438         auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
439         if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
440             // Re-create the PalmRejector because the device info has changed.
441             mPalmRejectors.erase(it);
442             mPalmRejectors.emplace(device.getId(), *info);
443         }
444         devicesToKeep.insert(device.getId());
445     }
446     // Delete all devices that we don't need to keep
447     std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
448         auto const& [deviceId, _] = item;
449         return devicesToKeep.find(deviceId) == devicesToKeep.end();
450     });
451     mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices);
452 }
453 
dump(std::string & dump)454 void UnwantedInteractionBlocker::dump(std::string& dump) {
455     std::scoped_lock lock(mLock);
456     dump += "UnwantedInteractionBlocker:\n";
457     dump += "  mPreferStylusOverTouchBlocker:\n";
458     dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), "    ");
459     dump += StringPrintf("  mEnablePalmRejection: %s\n",
460                          std::to_string(mEnablePalmRejection).c_str());
461     dump += StringPrintf("  isPalmRejectionEnabled (flag value): %s\n",
462                          std::to_string(isPalmRejectionEnabled()).c_str());
463     dump += mPalmRejectors.empty() ? "  mPalmRejectors: None\n" : "  mPalmRejectors:\n";
464     for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
465         dump += StringPrintf("    deviceId = %" PRId32 ":\n", deviceId);
466         dump += addLinePrefix(palmRejector.dump(), "      ");
467     }
468 }
469 
monitor()470 void UnwantedInteractionBlocker::monitor() {
471     std::scoped_lock lock(mLock);
472 }
473 
~UnwantedInteractionBlocker()474 UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
475 
update(const NotifyMotionArgs & args)476 void SlotState::update(const NotifyMotionArgs& args) {
477     for (size_t i = 0; i < args.getPointerCount(); i++) {
478         const int32_t pointerId = args.pointerProperties[i].id;
479         const int32_t resolvedAction = resolveActionForPointer(i, args.action);
480         processPointerId(pointerId, resolvedAction);
481     }
482 }
483 
findUnusedSlot() const484 size_t SlotState::findUnusedSlot() const {
485     size_t unusedSlot = 0;
486     // Since the collection is ordered, we can rely on the in-order traversal
487     for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
488         if (unusedSlot != slot) {
489             break;
490         }
491         unusedSlot++;
492     }
493     return unusedSlot;
494 }
495 
processPointerId(int pointerId,int32_t actionMasked)496 void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
497     switch (MotionEvent::getActionMasked(actionMasked)) {
498         case AMOTION_EVENT_ACTION_DOWN:
499         case AMOTION_EVENT_ACTION_POINTER_DOWN:
500         case AMOTION_EVENT_ACTION_HOVER_ENTER: {
501             // New pointer going down
502             size_t newSlot = findUnusedSlot();
503             mPointerIdsBySlot[newSlot] = pointerId;
504             mSlotsByPointerId[pointerId] = newSlot;
505             return;
506         }
507         case AMOTION_EVENT_ACTION_MOVE:
508         case AMOTION_EVENT_ACTION_HOVER_MOVE: {
509             return;
510         }
511         case AMOTION_EVENT_ACTION_CANCEL:
512         case AMOTION_EVENT_ACTION_POINTER_UP:
513         case AMOTION_EVENT_ACTION_UP:
514         case AMOTION_EVENT_ACTION_HOVER_EXIT: {
515             auto it = mSlotsByPointerId.find(pointerId);
516             LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
517             size_t slot = it->second;
518             // Erase this pointer from both collections
519             mPointerIdsBySlot.erase(slot);
520             mSlotsByPointerId.erase(pointerId);
521             return;
522         }
523     }
524     LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
525     return;
526 }
527 
getSlotForPointerId(int32_t pointerId) const528 std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
529     auto it = mSlotsByPointerId.find(pointerId);
530     if (it == mSlotsByPointerId.end()) {
531         return std::nullopt;
532     }
533     return it->second;
534 }
535 
dump() const536 std::string SlotState::dump() const {
537     std::string out = "mSlotsByPointerId:\n";
538     out += addLinePrefix(dumpMap(mSlotsByPointerId), "  ") + "\n";
539     out += "mPointerIdsBySlot:\n";
540     out += addLinePrefix(dumpMap(mPointerIdsBySlot), "  ") + "\n";
541     return out;
542 }
543 
544 class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
545 public:
AndroidPalmRejectionModel()546     AndroidPalmRejectionModel()
547           : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
548                                                                      std::vector<float>()) {
549         config_.resample_period = ::ui::kResamplePeriod;
550     }
551 };
552 
PalmRejector(const AndroidPalmFilterDeviceInfo & info,std::unique_ptr<::ui::PalmDetectionFilter> filter)553 PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
554                            std::unique_ptr<::ui::PalmDetectionFilter> filter)
555       : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
556         mDeviceInfo(info),
557         mPalmDetectionFilter(std::move(filter)) {
558     if (mPalmDetectionFilter != nullptr) {
559         // This path is used for testing. Non-testing invocations should let this constructor
560         // create a real PalmDetectionFilter
561         return;
562     }
563     std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
564             std::make_unique<AndroidPalmRejectionModel>();
565     mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
566                                                                       mSharedPalmState.get());
567 }
568 
getTouches(const NotifyMotionArgs & args,const AndroidPalmFilterDeviceInfo & deviceInfo,const SlotState & oldSlotState,const SlotState & newSlotState)569 std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
570                                                    const AndroidPalmFilterDeviceInfo& deviceInfo,
571                                                    const SlotState& oldSlotState,
572                                                    const SlotState& newSlotState) {
573     std::vector<::ui::InProgressTouchEvdev> touches;
574 
575     for (size_t i = 0; i < args.getPointerCount(); i++) {
576         const int32_t pointerId = args.pointerProperties[i].id;
577         touches.emplace_back(::ui::InProgressTouchEvdev());
578         touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
579         touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
580         // The field 'tool_type' is not used for palm rejection
581 
582         // Whether there is new information for the touch.
583         touches.back().altered = true;
584 
585         // Whether the touch was cancelled. Touch events should be ignored till a
586         // new touch is initiated.
587         touches.back().was_cancelled = false;
588 
589         // Whether the touch is going to be canceled.
590         touches.back().cancelled = false;
591 
592         // Whether the touch is delayed at first appearance. Will not be reported yet.
593         touches.back().delayed = false;
594 
595         // Whether the touch was delayed before.
596         touches.back().was_delayed = false;
597 
598         // Whether the touch is held until end or no longer held.
599         touches.back().held = false;
600 
601         // Whether this touch was held before being sent.
602         touches.back().was_held = false;
603 
604         const int32_t resolvedAction = resolveActionForPointer(i, args.action);
605         const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
606                 resolvedAction == AMOTION_EVENT_ACTION_DOWN;
607         touches.back().was_touching = !isDown;
608 
609         const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
610                 resolvedAction == AMOTION_EVENT_ACTION_UP ||
611                 resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
612 
613         touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
614         touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
615 
616         std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
617         if (!slot) {
618             slot = oldSlotState.getSlotForPointerId(pointerId);
619         }
620         LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
621         touches.back().slot = *slot;
622         touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
623         touches.back().touching = !isUpOrCancel;
624 
625         // The fields 'radius_x' and 'radius_x' are not used for palm rejection
626         touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
627         touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
628         // The field 'orientation' is not used for palm rejection
629         // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
630         // The field 'reported_tool_type' is not used for palm rejection
631         touches.back().stylus_button = false;
632     }
633     return touches;
634 }
635 
detectPalmPointers(const NotifyMotionArgs & args)636 std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
637     std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
638     std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
639 
640     // Store the slot state before we call getTouches and update it. This way, we can find
641     // the slots that have been removed due to the incoming event.
642     SlotState oldSlotState = mSlotState;
643     mSlotState.update(args);
644 
645     std::vector<::ui::InProgressTouchEvdev> touches =
646             getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
647     ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
648 
649     if (DEBUG_MODEL) {
650         std::stringstream touchesStream;
651         for (const ::ui::InProgressTouchEvdev& touch : touches) {
652             touchesStream << touch.tracking_id << " : " << touch << "\n";
653         }
654         ALOGD("Filter: touches = %s", touchesStream.str().c_str());
655     }
656 
657     mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
658 
659     ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
660              slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
661 
662     // Now that we know which slots should be suppressed, let's convert those to pointer id's.
663     std::set<int32_t> newSuppressedIds;
664     for (size_t i = 0; i < args.getPointerCount(); i++) {
665         const int32_t pointerId = args.pointerProperties[i].id;
666         std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
667         if (!slot) {
668             slot = mSlotState.getSlotForPointerId(pointerId);
669             LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
670         }
671         if (slotsToSuppress.test(*slot)) {
672             newSuppressedIds.insert(pointerId);
673         }
674     }
675     return newSuppressedIds;
676 }
677 
processMotion(const NotifyMotionArgs & args)678 std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
679     if (mPalmDetectionFilter == nullptr) {
680         return {args};
681     }
682     const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
683             args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
684             args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
685             args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
686             args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
687             args.action == AMOTION_EVENT_ACTION_SCROLL;
688     if (skipThisEvent) {
689         // Lets not process hover events, button events, or scroll for now.
690         return {args};
691     }
692     if (args.action == AMOTION_EVENT_ACTION_DOWN) {
693         mSuppressedPointerIds.clear();
694     }
695 
696     std::set<int32_t> oldSuppressedIds;
697     std::swap(oldSuppressedIds, mSuppressedPointerIds);
698 
699     std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
700     if (touchOnlyArgs) {
701         mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
702     } else {
703         // This is a stylus-only event.
704         // We can skip this event and just keep the suppressed pointer ids the same as before.
705         mSuppressedPointerIds = oldSuppressedIds;
706     }
707 
708     std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
709             cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
710     for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
711         LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
712     }
713 
714     // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
715     // subset of oldSuppressedIds.
716     if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
717                        mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
718         ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
719               dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
720               args.dump().c_str());
721     }
722 
723     return argsWithoutUnwantedPointers;
724 }
725 
getPalmFilterDeviceInfo() const726 const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
727     return mDeviceInfo;
728 }
729 
dump() const730 std::string PalmRejector::dump() const {
731     std::string out;
732     out += "mDeviceInfo:\n";
733     std::stringstream deviceInfo;
734     deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
735                << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
736     out += addLinePrefix(deviceInfo.str(), "  ");
737     out += "mSlotState:\n";
738     out += addLinePrefix(mSlotState.dump(), "  ");
739     out += "mSuppressedPointerIds: ";
740     out += dumpSet(mSuppressedPointerIds) + "\n";
741     std::stringstream state;
742     state << *mSharedPalmState;
743     out += "mSharedPalmState: " + state.str() + "\n";
744     std::stringstream filter;
745     filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
746     out += "mPalmDetectionFilter:\n";
747     out += addLinePrefix(filter.str(), "  ") + "\n";
748     return out;
749 }
750 
751 } // namespace android
752