• 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 #include "PreferStylusOverTouchBlocker.h"
18 #include <input/PrintTools.h>
19 
20 namespace android {
21 
checkToolType(const NotifyMotionArgs & args)22 static std::pair<bool, bool> checkToolType(const NotifyMotionArgs& args) {
23     bool hasStylus = false;
24     bool hasTouch = false;
25     for (size_t i = 0; i < args.pointerCount; i++) {
26         // Make sure we are canceling stylus pointers
27         const int32_t toolType = args.pointerProperties[i].toolType;
28         if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
29             toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
30             hasStylus = true;
31         }
32         if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
33             hasTouch = true;
34         }
35     }
36     return std::make_pair(hasTouch, hasStylus);
37 }
38 
39 /**
40  * Intersect two sets in-place, storing the result in 'set1'.
41  * Find elements in set1 that are not present in set2 and delete them,
42  * relying on the fact that the two sets are ordered.
43  */
44 template <typename T>
intersectInPlace(std::set<T> & set1,const std::set<T> & set2)45 static void intersectInPlace(std::set<T>& set1, const std::set<T>& set2) {
46     typename std::set<T>::iterator it1 = set1.begin();
47     typename std::set<T>::const_iterator it2 = set2.begin();
48     while (it1 != set1.end() && it2 != set2.end()) {
49         const T& element1 = *it1;
50         const T& element2 = *it2;
51         if (element1 < element2) {
52             // This element is not present in set2. Remove it from set1.
53             it1 = set1.erase(it1);
54             continue;
55         }
56         if (element2 < element1) {
57             it2++;
58         }
59         if (element1 == element2) {
60             it1++;
61             it2++;
62         }
63     }
64     // Remove the rest of the elements in set1 because set2 is already exhausted.
65     set1.erase(it1, set1.end());
66 }
67 
68 /**
69  * Same as above, but prune a map
70  */
71 template <typename K, class V>
intersectInPlace(std::map<K,V> & map,const std::set<K> & set2)72 static void intersectInPlace(std::map<K, V>& map, const std::set<K>& set2) {
73     typename std::map<K, V>::iterator it1 = map.begin();
74     typename std::set<K>::const_iterator it2 = set2.begin();
75     while (it1 != map.end() && it2 != set2.end()) {
76         const auto& [key, _] = *it1;
77         const K& element2 = *it2;
78         if (key < element2) {
79             // This element is not present in set2. Remove it from map.
80             it1 = map.erase(it1);
81             continue;
82         }
83         if (element2 < key) {
84             it2++;
85         }
86         if (key == element2) {
87             it1++;
88             it2++;
89         }
90     }
91     // Remove the rest of the elements in map because set2 is already exhausted.
92     map.erase(it1, map.end());
93 }
94 
95 // -------------------------------- PreferStylusOverTouchBlocker -----------------------------------
96 
processMotion(const NotifyMotionArgs & args)97 std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
98         const NotifyMotionArgs& args) {
99     const auto [hasTouch, hasStylus] = checkToolType(args);
100     const bool isUpOrCancel =
101             args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL;
102 
103     if (hasTouch && hasStylus) {
104         mDevicesWithMixedToolType.insert(args.deviceId);
105     }
106     // Handle the case where mixed touch and stylus pointers are reported. Add this device to the
107     // ignore list, since it clearly supports simultaneous touch and stylus.
108     if (mDevicesWithMixedToolType.find(args.deviceId) != mDevicesWithMixedToolType.end()) {
109         // This event comes from device with mixed stylus and touch event. Ignore this device.
110         if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
111             // If we started to cancel events from this device, continue to do so to keep
112             // the stream consistent. It should happen at most once per "mixed" device.
113             if (isUpOrCancel) {
114                 mCanceledDevices.erase(args.deviceId);
115                 mLastTouchEvents.erase(args.deviceId);
116             }
117             return {};
118         }
119         return {args};
120     }
121 
122     const bool isStylusEvent = hasStylus;
123     const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
124 
125     if (isStylusEvent) {
126         if (isDown) {
127             // Reject all touch while stylus is down
128             mActiveStyli.insert(args.deviceId);
129 
130             // Cancel all current touch!
131             std::vector<NotifyMotionArgs> result;
132             for (auto& [deviceId, lastTouchEvent] : mLastTouchEvents) {
133                 if (mCanceledDevices.find(deviceId) != mCanceledDevices.end()) {
134                     // Already canceled, go to next one.
135                     continue;
136                 }
137                 // Not yet canceled. Cancel it.
138                 lastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL;
139                 lastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED;
140                 lastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
141                 result.push_back(lastTouchEvent);
142                 mCanceledDevices.insert(deviceId);
143             }
144             result.push_back(args);
145             return result;
146         }
147         if (isUpOrCancel) {
148             mActiveStyli.erase(args.deviceId);
149         }
150         // Never drop stylus events
151         return {args};
152     }
153 
154     const bool isTouchEvent = hasTouch;
155     if (isTouchEvent) {
156         // Suppress the current gesture if any stylus is still down
157         if (!mActiveStyli.empty()) {
158             mCanceledDevices.insert(args.deviceId);
159         }
160 
161         const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
162         if (isUpOrCancel) {
163             mCanceledDevices.erase(args.deviceId);
164             mLastTouchEvents.erase(args.deviceId);
165         }
166 
167         // If we already canceled the current gesture, then continue to drop events from it, even if
168         // the stylus has been lifted.
169         if (shouldDrop) {
170             return {};
171         }
172 
173         if (!isUpOrCancel) {
174             mLastTouchEvents[args.deviceId] = args;
175         }
176         return {args};
177     }
178 
179     // Not a touch or stylus event
180     return {args};
181 }
182 
notifyInputDevicesChanged(const std::vector<InputDeviceInfo> & inputDevices)183 void PreferStylusOverTouchBlocker::notifyInputDevicesChanged(
184         const std::vector<InputDeviceInfo>& inputDevices) {
185     std::set<int32_t> presentDevices;
186     for (const InputDeviceInfo& device : inputDevices) {
187         presentDevices.insert(device.getId());
188     }
189     // Only keep the devices that are still present.
190     intersectInPlace(mDevicesWithMixedToolType, presentDevices);
191     intersectInPlace(mLastTouchEvents, presentDevices);
192     intersectInPlace(mCanceledDevices, presentDevices);
193     intersectInPlace(mActiveStyli, presentDevices);
194 }
195 
notifyDeviceReset(const NotifyDeviceResetArgs & args)196 void PreferStylusOverTouchBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
197     mDevicesWithMixedToolType.erase(args.deviceId);
198     mLastTouchEvents.erase(args.deviceId);
199     mCanceledDevices.erase(args.deviceId);
200     mActiveStyli.erase(args.deviceId);
201 }
202 
dumpArgs(const NotifyMotionArgs & args)203 static std::string dumpArgs(const NotifyMotionArgs& args) {
204     return args.dump();
205 }
206 
dump() const207 std::string PreferStylusOverTouchBlocker::dump() const {
208     std::string out;
209     out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n";
210     out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n";
211     out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n";
212     out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n";
213     return out;
214 }
215 
216 } // namespace android
217