xref: /aosp_15_r20/frameworks/native/services/inputflinger/PreferStylusOverTouchBlocker.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1*38e8c45fSAndroid Build Coastguard Worker /*
2*38e8c45fSAndroid Build Coastguard Worker  * Copyright (C) 2022 The Android Open Source Project
3*38e8c45fSAndroid Build Coastguard Worker  *
4*38e8c45fSAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*38e8c45fSAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*38e8c45fSAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*38e8c45fSAndroid Build Coastguard Worker  *
8*38e8c45fSAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*38e8c45fSAndroid Build Coastguard Worker  *
10*38e8c45fSAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*38e8c45fSAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*38e8c45fSAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*38e8c45fSAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*38e8c45fSAndroid Build Coastguard Worker  * limitations under the License.
15*38e8c45fSAndroid Build Coastguard Worker  */
16*38e8c45fSAndroid Build Coastguard Worker 
17*38e8c45fSAndroid Build Coastguard Worker #include "PreferStylusOverTouchBlocker.h"
18*38e8c45fSAndroid Build Coastguard Worker #include <com_android_input_flags.h>
19*38e8c45fSAndroid Build Coastguard Worker #include <input/PrintTools.h>
20*38e8c45fSAndroid Build Coastguard Worker 
21*38e8c45fSAndroid Build Coastguard Worker namespace input_flags = com::android::input::flags;
22*38e8c45fSAndroid Build Coastguard Worker 
23*38e8c45fSAndroid Build Coastguard Worker namespace android {
24*38e8c45fSAndroid Build Coastguard Worker 
25*38e8c45fSAndroid Build Coastguard Worker const bool BLOCK_TOUCH_WHEN_STYLUS_HOVER = !input_flags::disable_reject_touch_on_stylus_hover();
26*38e8c45fSAndroid Build Coastguard Worker 
checkToolType(const NotifyMotionArgs & args)27*38e8c45fSAndroid Build Coastguard Worker static std::pair<bool, bool> checkToolType(const NotifyMotionArgs& args) {
28*38e8c45fSAndroid Build Coastguard Worker     bool hasStylus = false;
29*38e8c45fSAndroid Build Coastguard Worker     bool hasTouch = false;
30*38e8c45fSAndroid Build Coastguard Worker     for (size_t i = 0; i < args.getPointerCount(); i++) {
31*38e8c45fSAndroid Build Coastguard Worker         // Make sure we are canceling stylus pointers
32*38e8c45fSAndroid Build Coastguard Worker         const ToolType toolType = args.pointerProperties[i].toolType;
33*38e8c45fSAndroid Build Coastguard Worker         if (isStylusToolType(toolType)) {
34*38e8c45fSAndroid Build Coastguard Worker             hasStylus = true;
35*38e8c45fSAndroid Build Coastguard Worker         }
36*38e8c45fSAndroid Build Coastguard Worker         if (toolType == ToolType::FINGER) {
37*38e8c45fSAndroid Build Coastguard Worker             hasTouch = true;
38*38e8c45fSAndroid Build Coastguard Worker         }
39*38e8c45fSAndroid Build Coastguard Worker     }
40*38e8c45fSAndroid Build Coastguard Worker     return std::make_pair(hasTouch, hasStylus);
41*38e8c45fSAndroid Build Coastguard Worker }
42*38e8c45fSAndroid Build Coastguard Worker 
43*38e8c45fSAndroid Build Coastguard Worker /**
44*38e8c45fSAndroid Build Coastguard Worker  * Intersect two sets in-place, storing the result in 'set1'.
45*38e8c45fSAndroid Build Coastguard Worker  * Find elements in set1 that are not present in set2 and delete them,
46*38e8c45fSAndroid Build Coastguard Worker  * relying on the fact that the two sets are ordered.
47*38e8c45fSAndroid Build Coastguard Worker  */
48*38e8c45fSAndroid Build Coastguard Worker template <typename T>
intersectInPlace(std::set<T> & set1,const std::set<T> & set2)49*38e8c45fSAndroid Build Coastguard Worker static void intersectInPlace(std::set<T>& set1, const std::set<T>& set2) {
50*38e8c45fSAndroid Build Coastguard Worker     typename std::set<T>::iterator it1 = set1.begin();
51*38e8c45fSAndroid Build Coastguard Worker     typename std::set<T>::const_iterator it2 = set2.begin();
52*38e8c45fSAndroid Build Coastguard Worker     while (it1 != set1.end() && it2 != set2.end()) {
53*38e8c45fSAndroid Build Coastguard Worker         const T& element1 = *it1;
54*38e8c45fSAndroid Build Coastguard Worker         const T& element2 = *it2;
55*38e8c45fSAndroid Build Coastguard Worker         if (element1 < element2) {
56*38e8c45fSAndroid Build Coastguard Worker             // This element is not present in set2. Remove it from set1.
57*38e8c45fSAndroid Build Coastguard Worker             it1 = set1.erase(it1);
58*38e8c45fSAndroid Build Coastguard Worker             continue;
59*38e8c45fSAndroid Build Coastguard Worker         }
60*38e8c45fSAndroid Build Coastguard Worker         if (element2 < element1) {
61*38e8c45fSAndroid Build Coastguard Worker             it2++;
62*38e8c45fSAndroid Build Coastguard Worker         }
63*38e8c45fSAndroid Build Coastguard Worker         if (element1 == element2) {
64*38e8c45fSAndroid Build Coastguard Worker             it1++;
65*38e8c45fSAndroid Build Coastguard Worker             it2++;
66*38e8c45fSAndroid Build Coastguard Worker         }
67*38e8c45fSAndroid Build Coastguard Worker     }
68*38e8c45fSAndroid Build Coastguard Worker     // Remove the rest of the elements in set1 because set2 is already exhausted.
69*38e8c45fSAndroid Build Coastguard Worker     set1.erase(it1, set1.end());
70*38e8c45fSAndroid Build Coastguard Worker }
71*38e8c45fSAndroid Build Coastguard Worker 
72*38e8c45fSAndroid Build Coastguard Worker /**
73*38e8c45fSAndroid Build Coastguard Worker  * Same as above, but prune a map
74*38e8c45fSAndroid Build Coastguard Worker  */
75*38e8c45fSAndroid Build Coastguard Worker template <typename K, class V>
intersectInPlace(std::map<K,V> & map,const std::set<K> & set2)76*38e8c45fSAndroid Build Coastguard Worker static void intersectInPlace(std::map<K, V>& map, const std::set<K>& set2) {
77*38e8c45fSAndroid Build Coastguard Worker     typename std::map<K, V>::iterator it1 = map.begin();
78*38e8c45fSAndroid Build Coastguard Worker     typename std::set<K>::const_iterator it2 = set2.begin();
79*38e8c45fSAndroid Build Coastguard Worker     while (it1 != map.end() && it2 != set2.end()) {
80*38e8c45fSAndroid Build Coastguard Worker         const auto& [key, _] = *it1;
81*38e8c45fSAndroid Build Coastguard Worker         const K& element2 = *it2;
82*38e8c45fSAndroid Build Coastguard Worker         if (key < element2) {
83*38e8c45fSAndroid Build Coastguard Worker             // This element is not present in set2. Remove it from map.
84*38e8c45fSAndroid Build Coastguard Worker             it1 = map.erase(it1);
85*38e8c45fSAndroid Build Coastguard Worker             continue;
86*38e8c45fSAndroid Build Coastguard Worker         }
87*38e8c45fSAndroid Build Coastguard Worker         if (element2 < key) {
88*38e8c45fSAndroid Build Coastguard Worker             it2++;
89*38e8c45fSAndroid Build Coastguard Worker         }
90*38e8c45fSAndroid Build Coastguard Worker         if (key == element2) {
91*38e8c45fSAndroid Build Coastguard Worker             it1++;
92*38e8c45fSAndroid Build Coastguard Worker             it2++;
93*38e8c45fSAndroid Build Coastguard Worker         }
94*38e8c45fSAndroid Build Coastguard Worker     }
95*38e8c45fSAndroid Build Coastguard Worker     // Remove the rest of the elements in map because set2 is already exhausted.
96*38e8c45fSAndroid Build Coastguard Worker     map.erase(it1, map.end());
97*38e8c45fSAndroid Build Coastguard Worker }
98*38e8c45fSAndroid Build Coastguard Worker 
99*38e8c45fSAndroid Build Coastguard Worker // -------------------------------- PreferStylusOverTouchBlocker -----------------------------------
100*38e8c45fSAndroid Build Coastguard Worker 
processMotion(const NotifyMotionArgs & args)101*38e8c45fSAndroid Build Coastguard Worker std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
102*38e8c45fSAndroid Build Coastguard Worker         const NotifyMotionArgs& args) {
103*38e8c45fSAndroid Build Coastguard Worker     const auto [hasTouch, hasStylus] = checkToolType(args);
104*38e8c45fSAndroid Build Coastguard Worker     const bool isDisengageOrCancel = BLOCK_TOUCH_WHEN_STYLUS_HOVER
105*38e8c45fSAndroid Build Coastguard Worker             ? (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
106*38e8c45fSAndroid Build Coastguard Worker                args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL)
107*38e8c45fSAndroid Build Coastguard Worker             : (args.action == AMOTION_EVENT_ACTION_UP ||
108*38e8c45fSAndroid Build Coastguard Worker                args.action == AMOTION_EVENT_ACTION_CANCEL);
109*38e8c45fSAndroid Build Coastguard Worker 
110*38e8c45fSAndroid Build Coastguard Worker     if (hasTouch && hasStylus) {
111*38e8c45fSAndroid Build Coastguard Worker         mDevicesWithMixedToolType.insert(args.deviceId);
112*38e8c45fSAndroid Build Coastguard Worker     }
113*38e8c45fSAndroid Build Coastguard Worker     // Handle the case where mixed touch and stylus pointers are reported. Add this device to the
114*38e8c45fSAndroid Build Coastguard Worker     // ignore list, since it clearly supports simultaneous touch and stylus.
115*38e8c45fSAndroid Build Coastguard Worker     if (mDevicesWithMixedToolType.find(args.deviceId) != mDevicesWithMixedToolType.end()) {
116*38e8c45fSAndroid Build Coastguard Worker         // This event comes from device with mixed stylus and touch event. Ignore this device.
117*38e8c45fSAndroid Build Coastguard Worker         if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
118*38e8c45fSAndroid Build Coastguard Worker             // If we started to cancel events from this device, continue to do so to keep
119*38e8c45fSAndroid Build Coastguard Worker             // the stream consistent. It should happen at most once per "mixed" device.
120*38e8c45fSAndroid Build Coastguard Worker             if (isDisengageOrCancel) {
121*38e8c45fSAndroid Build Coastguard Worker                 mCanceledDevices.erase(args.deviceId);
122*38e8c45fSAndroid Build Coastguard Worker                 mLastTouchEvents.erase(args.deviceId);
123*38e8c45fSAndroid Build Coastguard Worker             }
124*38e8c45fSAndroid Build Coastguard Worker             return {};
125*38e8c45fSAndroid Build Coastguard Worker         }
126*38e8c45fSAndroid Build Coastguard Worker         return {args};
127*38e8c45fSAndroid Build Coastguard Worker     }
128*38e8c45fSAndroid Build Coastguard Worker 
129*38e8c45fSAndroid Build Coastguard Worker     const bool isStylusEvent = hasStylus;
130*38e8c45fSAndroid Build Coastguard Worker     const bool isEngage = BLOCK_TOUCH_WHEN_STYLUS_HOVER
131*38e8c45fSAndroid Build Coastguard Worker             ? (args.action == AMOTION_EVENT_ACTION_DOWN ||
132*38e8c45fSAndroid Build Coastguard Worker                args.action == AMOTION_EVENT_ACTION_HOVER_ENTER)
133*38e8c45fSAndroid Build Coastguard Worker             : (args.action == AMOTION_EVENT_ACTION_DOWN);
134*38e8c45fSAndroid Build Coastguard Worker 
135*38e8c45fSAndroid Build Coastguard Worker     if (isStylusEvent) {
136*38e8c45fSAndroid Build Coastguard Worker         if (isEngage) {
137*38e8c45fSAndroid Build Coastguard Worker             // Reject all touch while stylus is down
138*38e8c45fSAndroid Build Coastguard Worker             mActiveStyli.insert(args.deviceId);
139*38e8c45fSAndroid Build Coastguard Worker 
140*38e8c45fSAndroid Build Coastguard Worker             // Cancel all current touch!
141*38e8c45fSAndroid Build Coastguard Worker             std::vector<NotifyMotionArgs> result;
142*38e8c45fSAndroid Build Coastguard Worker             for (auto& [deviceId, lastTouchEvent] : mLastTouchEvents) {
143*38e8c45fSAndroid Build Coastguard Worker                 if (mCanceledDevices.find(deviceId) != mCanceledDevices.end()) {
144*38e8c45fSAndroid Build Coastguard Worker                     // Already canceled, go to next one.
145*38e8c45fSAndroid Build Coastguard Worker                     continue;
146*38e8c45fSAndroid Build Coastguard Worker                 }
147*38e8c45fSAndroid Build Coastguard Worker                 // Not yet canceled. Cancel it.
148*38e8c45fSAndroid Build Coastguard Worker                 lastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL;
149*38e8c45fSAndroid Build Coastguard Worker                 lastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED;
150*38e8c45fSAndroid Build Coastguard Worker                 lastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
151*38e8c45fSAndroid Build Coastguard Worker                 result.push_back(lastTouchEvent);
152*38e8c45fSAndroid Build Coastguard Worker                 mCanceledDevices.insert(deviceId);
153*38e8c45fSAndroid Build Coastguard Worker             }
154*38e8c45fSAndroid Build Coastguard Worker             result.push_back(args);
155*38e8c45fSAndroid Build Coastguard Worker             return result;
156*38e8c45fSAndroid Build Coastguard Worker         }
157*38e8c45fSAndroid Build Coastguard Worker         if (isDisengageOrCancel) {
158*38e8c45fSAndroid Build Coastguard Worker             mActiveStyli.erase(args.deviceId);
159*38e8c45fSAndroid Build Coastguard Worker         }
160*38e8c45fSAndroid Build Coastguard Worker         // Never drop stylus events
161*38e8c45fSAndroid Build Coastguard Worker         return {args};
162*38e8c45fSAndroid Build Coastguard Worker     }
163*38e8c45fSAndroid Build Coastguard Worker 
164*38e8c45fSAndroid Build Coastguard Worker     const bool isTouchEvent = hasTouch;
165*38e8c45fSAndroid Build Coastguard Worker     if (isTouchEvent) {
166*38e8c45fSAndroid Build Coastguard Worker         // Suppress the current gesture if any stylus is still down
167*38e8c45fSAndroid Build Coastguard Worker         if (!mActiveStyli.empty()) {
168*38e8c45fSAndroid Build Coastguard Worker             mCanceledDevices.insert(args.deviceId);
169*38e8c45fSAndroid Build Coastguard Worker         }
170*38e8c45fSAndroid Build Coastguard Worker 
171*38e8c45fSAndroid Build Coastguard Worker         const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
172*38e8c45fSAndroid Build Coastguard Worker         if (isDisengageOrCancel) {
173*38e8c45fSAndroid Build Coastguard Worker             mCanceledDevices.erase(args.deviceId);
174*38e8c45fSAndroid Build Coastguard Worker             mLastTouchEvents.erase(args.deviceId);
175*38e8c45fSAndroid Build Coastguard Worker         }
176*38e8c45fSAndroid Build Coastguard Worker 
177*38e8c45fSAndroid Build Coastguard Worker         // If we already canceled the current gesture, then continue to drop events from it, even if
178*38e8c45fSAndroid Build Coastguard Worker         // the stylus has been lifted.
179*38e8c45fSAndroid Build Coastguard Worker         if (shouldDrop) {
180*38e8c45fSAndroid Build Coastguard Worker             return {};
181*38e8c45fSAndroid Build Coastguard Worker         }
182*38e8c45fSAndroid Build Coastguard Worker 
183*38e8c45fSAndroid Build Coastguard Worker         if (!isDisengageOrCancel) {
184*38e8c45fSAndroid Build Coastguard Worker             mLastTouchEvents[args.deviceId] = args;
185*38e8c45fSAndroid Build Coastguard Worker         }
186*38e8c45fSAndroid Build Coastguard Worker         return {args};
187*38e8c45fSAndroid Build Coastguard Worker     }
188*38e8c45fSAndroid Build Coastguard Worker 
189*38e8c45fSAndroid Build Coastguard Worker     // Not a touch or stylus event
190*38e8c45fSAndroid Build Coastguard Worker     return {args};
191*38e8c45fSAndroid Build Coastguard Worker }
192*38e8c45fSAndroid Build Coastguard Worker 
notifyInputDevicesChanged(const std::vector<InputDeviceInfo> & inputDevices)193*38e8c45fSAndroid Build Coastguard Worker void PreferStylusOverTouchBlocker::notifyInputDevicesChanged(
194*38e8c45fSAndroid Build Coastguard Worker         const std::vector<InputDeviceInfo>& inputDevices) {
195*38e8c45fSAndroid Build Coastguard Worker     std::set<int32_t> presentDevices;
196*38e8c45fSAndroid Build Coastguard Worker     for (const InputDeviceInfo& device : inputDevices) {
197*38e8c45fSAndroid Build Coastguard Worker         presentDevices.insert(device.getId());
198*38e8c45fSAndroid Build Coastguard Worker     }
199*38e8c45fSAndroid Build Coastguard Worker     // Only keep the devices that are still present.
200*38e8c45fSAndroid Build Coastguard Worker     intersectInPlace(mDevicesWithMixedToolType, presentDevices);
201*38e8c45fSAndroid Build Coastguard Worker     intersectInPlace(mLastTouchEvents, presentDevices);
202*38e8c45fSAndroid Build Coastguard Worker     intersectInPlace(mCanceledDevices, presentDevices);
203*38e8c45fSAndroid Build Coastguard Worker     intersectInPlace(mActiveStyli, presentDevices);
204*38e8c45fSAndroid Build Coastguard Worker }
205*38e8c45fSAndroid Build Coastguard Worker 
notifyDeviceReset(const NotifyDeviceResetArgs & args)206*38e8c45fSAndroid Build Coastguard Worker void PreferStylusOverTouchBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
207*38e8c45fSAndroid Build Coastguard Worker     mDevicesWithMixedToolType.erase(args.deviceId);
208*38e8c45fSAndroid Build Coastguard Worker     mLastTouchEvents.erase(args.deviceId);
209*38e8c45fSAndroid Build Coastguard Worker     mCanceledDevices.erase(args.deviceId);
210*38e8c45fSAndroid Build Coastguard Worker     mActiveStyli.erase(args.deviceId);
211*38e8c45fSAndroid Build Coastguard Worker }
212*38e8c45fSAndroid Build Coastguard Worker 
dumpArgs(const NotifyMotionArgs & args)213*38e8c45fSAndroid Build Coastguard Worker static std::string dumpArgs(const NotifyMotionArgs& args) {
214*38e8c45fSAndroid Build Coastguard Worker     return args.dump();
215*38e8c45fSAndroid Build Coastguard Worker }
216*38e8c45fSAndroid Build Coastguard Worker 
dump() const217*38e8c45fSAndroid Build Coastguard Worker std::string PreferStylusOverTouchBlocker::dump() const {
218*38e8c45fSAndroid Build Coastguard Worker     std::string out;
219*38e8c45fSAndroid Build Coastguard Worker     out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n";
220*38e8c45fSAndroid Build Coastguard Worker     out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n";
221*38e8c45fSAndroid Build Coastguard Worker     out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n";
222*38e8c45fSAndroid Build Coastguard Worker     out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n";
223*38e8c45fSAndroid Build Coastguard Worker     return out;
224*38e8c45fSAndroid Build Coastguard Worker }
225*38e8c45fSAndroid Build Coastguard Worker 
226*38e8c45fSAndroid Build Coastguard Worker } // namespace android
227