blob: fbd296c131b3a0fbd978328fbe069b79ad971a00 [file] [log] [blame]
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -08001/*
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"
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080018#include <input/PrintTools.h>
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080019
20namespace android {
21
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080022static 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
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -070027 const ToolType toolType = args.pointerProperties[i].toolType;
Prabir Pradhane5626962022-10-27 20:30:53 +000028 if (isStylusToolType(toolType)) {
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080029 hasStylus = true;
30 }
Siarhei Vishniakou6d73f832022-07-21 17:27:03 -070031 if (toolType == ToolType::FINGER) {
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080032 hasTouch = true;
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080033 }
34 }
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080035 return std::make_pair(hasTouch, hasStylus);
36}
37
38/**
39 * Intersect two sets in-place, storing the result in 'set1'.
40 * Find elements in set1 that are not present in set2 and delete them,
41 * relying on the fact that the two sets are ordered.
42 */
43template <typename T>
44static void intersectInPlace(std::set<T>& set1, const std::set<T>& set2) {
45 typename std::set<T>::iterator it1 = set1.begin();
46 typename std::set<T>::const_iterator it2 = set2.begin();
47 while (it1 != set1.end() && it2 != set2.end()) {
48 const T& element1 = *it1;
49 const T& element2 = *it2;
50 if (element1 < element2) {
51 // This element is not present in set2. Remove it from set1.
52 it1 = set1.erase(it1);
53 continue;
54 }
55 if (element2 < element1) {
56 it2++;
57 }
58 if (element1 == element2) {
59 it1++;
60 it2++;
61 }
62 }
63 // Remove the rest of the elements in set1 because set2 is already exhausted.
64 set1.erase(it1, set1.end());
65}
66
67/**
68 * Same as above, but prune a map
69 */
70template <typename K, class V>
71static void intersectInPlace(std::map<K, V>& map, const std::set<K>& set2) {
72 typename std::map<K, V>::iterator it1 = map.begin();
73 typename std::set<K>::const_iterator it2 = set2.begin();
74 while (it1 != map.end() && it2 != set2.end()) {
75 const auto& [key, _] = *it1;
76 const K& element2 = *it2;
77 if (key < element2) {
78 // This element is not present in set2. Remove it from map.
79 it1 = map.erase(it1);
80 continue;
81 }
82 if (element2 < key) {
83 it2++;
84 }
85 if (key == element2) {
86 it1++;
87 it2++;
88 }
89 }
90 // Remove the rest of the elements in map because set2 is already exhausted.
91 map.erase(it1, map.end());
92}
93
94// -------------------------------- PreferStylusOverTouchBlocker -----------------------------------
95
96std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
97 const NotifyMotionArgs& args) {
98 const auto [hasTouch, hasStylus] = checkToolType(args);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080099 const bool isUpOrCancel =
100 args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL;
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800101
102 if (hasTouch && hasStylus) {
103 mDevicesWithMixedToolType.insert(args.deviceId);
104 }
105 // Handle the case where mixed touch and stylus pointers are reported. Add this device to the
106 // ignore list, since it clearly supports simultaneous touch and stylus.
107 if (mDevicesWithMixedToolType.find(args.deviceId) != mDevicesWithMixedToolType.end()) {
108 // This event comes from device with mixed stylus and touch event. Ignore this device.
109 if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
110 // If we started to cancel events from this device, continue to do so to keep
111 // the stream consistent. It should happen at most once per "mixed" device.
112 if (isUpOrCancel) {
113 mCanceledDevices.erase(args.deviceId);
114 mLastTouchEvents.erase(args.deviceId);
115 }
116 return {};
117 }
118 return {args};
119 }
120
121 const bool isStylusEvent = hasStylus;
122 const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
123
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800124 if (isStylusEvent) {
125 if (isDown) {
126 // Reject all touch while stylus is down
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800127 mActiveStyli.insert(args.deviceId);
128
129 // Cancel all current touch!
130 std::vector<NotifyMotionArgs> result;
131 for (auto& [deviceId, lastTouchEvent] : mLastTouchEvents) {
132 if (mCanceledDevices.find(deviceId) != mCanceledDevices.end()) {
133 // Already canceled, go to next one.
134 continue;
135 }
136 // Not yet canceled. Cancel it.
137 lastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL;
138 lastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED;
139 lastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
140 result.push_back(lastTouchEvent);
141 mCanceledDevices.insert(deviceId);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800142 }
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800143 result.push_back(args);
144 return result;
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800145 }
146 if (isUpOrCancel) {
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800147 mActiveStyli.erase(args.deviceId);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800148 }
149 // Never drop stylus events
150 return {args};
151 }
152
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800153 const bool isTouchEvent = hasTouch;
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800154 if (isTouchEvent) {
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800155 // Suppress the current gesture if any stylus is still down
156 if (!mActiveStyli.empty()) {
157 mCanceledDevices.insert(args.deviceId);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800158 }
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800159
160 const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
161 if (isUpOrCancel) {
162 mCanceledDevices.erase(args.deviceId);
163 mLastTouchEvents.erase(args.deviceId);
164 }
165
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800166 // If we already canceled the current gesture, then continue to drop events from it, even if
167 // the stylus has been lifted.
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800168 if (shouldDrop) {
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800169 return {};
170 }
171
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800172 if (!isUpOrCancel) {
173 mLastTouchEvents[args.deviceId] = args;
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800174 }
175 return {args};
176 }
177
178 // Not a touch or stylus event
179 return {args};
180}
181
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800182void PreferStylusOverTouchBlocker::notifyInputDevicesChanged(
183 const std::vector<InputDeviceInfo>& inputDevices) {
184 std::set<int32_t> presentDevices;
185 for (const InputDeviceInfo& device : inputDevices) {
186 presentDevices.insert(device.getId());
187 }
188 // Only keep the devices that are still present.
189 intersectInPlace(mDevicesWithMixedToolType, presentDevices);
190 intersectInPlace(mLastTouchEvents, presentDevices);
191 intersectInPlace(mCanceledDevices, presentDevices);
192 intersectInPlace(mActiveStyli, presentDevices);
193}
194
195void PreferStylusOverTouchBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
196 mDevicesWithMixedToolType.erase(args.deviceId);
197 mLastTouchEvents.erase(args.deviceId);
198 mCanceledDevices.erase(args.deviceId);
199 mActiveStyli.erase(args.deviceId);
200}
201
202static std::string dumpArgs(const NotifyMotionArgs& args) {
203 return args.dump();
204}
205
206std::string PreferStylusOverTouchBlocker::dump() const {
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800207 std::string out;
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800208 out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n";
209 out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n";
210 out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n";
211 out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n";
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800212 return out;
213}
214
215} // namespace android