1 /*
2  * Copyright (C) 2020 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 package com.android.systemui.classifier;
18 
19 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_X_PRIMARY_DEVIANCE;
20 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE;
21 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
22 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
23 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
24 import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
25 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
26 
27 import android.graphics.Point;
28 import android.provider.DeviceConfig;
29 import android.view.MotionEvent;
30 
31 import com.android.systemui.util.DeviceConfigProxy;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Locale;
36 
37 import javax.inject.Inject;
38 
39 /**
40  * Penalizes gestures that change direction in either the x or y too much.
41  */
42 class ZigZagClassifier extends FalsingClassifier {
43 
44     // Define how far one can move back and forth over one inch of travel before being falsed.
45     // `PRIMARY` defines how far one can deviate in the primary direction of travel. I.e. if you're
46     // swiping vertically, you shouldn't have a lot of zig zag in the vertical direction. Since
47     // most swipes will follow somewhat of a 'C' or 'S' shape, we allow more deviance along the
48     // `SECONDARY` axis.
49     private static final float MAX_X_PRIMARY_DEVIANCE = .05f;
50     private static final float MAX_Y_PRIMARY_DEVIANCE = .15f;
51     private static final float MAX_X_SECONDARY_DEVIANCE = .4f;
52     private static final float MAX_Y_SECONDARY_DEVIANCE = .3f;
53 
54     private final float mMaxXPrimaryDeviance;
55     private final float mMaxYPrimaryDeviance;
56     private final float mMaxXSecondaryDeviance;
57     private final float mMaxYSecondaryDeviance;
58     private float mLastDevianceX;
59     private float mLastDevianceY;
60     private float mLastMaxXDeviance;
61     private float mLastMaxYDeviance;
62 
63     @Inject
ZigZagClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy)64     ZigZagClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
65         super(dataProvider);
66 
67         mMaxXPrimaryDeviance = deviceConfigProxy.getFloat(
68                 DeviceConfig.NAMESPACE_SYSTEMUI,
69                 BRIGHTLINE_FALSING_ZIGZAG_X_PRIMARY_DEVIANCE,
70                 MAX_X_PRIMARY_DEVIANCE);
71 
72         mMaxYPrimaryDeviance = deviceConfigProxy.getFloat(
73                 DeviceConfig.NAMESPACE_SYSTEMUI,
74                 BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE,
75                 MAX_Y_PRIMARY_DEVIANCE);
76 
77         mMaxXSecondaryDeviance = deviceConfigProxy.getFloat(
78                 DeviceConfig.NAMESPACE_SYSTEMUI,
79                 BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE,
80                 MAX_X_SECONDARY_DEVIANCE);
81 
82         mMaxYSecondaryDeviance = deviceConfigProxy.getFloat(
83                 DeviceConfig.NAMESPACE_SYSTEMUI,
84                 BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE,
85                 MAX_Y_SECONDARY_DEVIANCE);
86 
87     }
88 
89     @Override
calculateFalsingResult( @lassifier.InteractionType int interactionType, double historyBelief, double historyConfidence)90     Result calculateFalsingResult(
91             @Classifier.InteractionType int interactionType,
92             double historyBelief, double historyConfidence) {
93         if (interactionType == BRIGHTNESS_SLIDER
94                 || interactionType == MEDIA_SEEKBAR
95                 || interactionType == SHADE_DRAG) {
96             return Result.passed(0);
97         }
98 
99         List<MotionEvent> motionEvents = getRecentMotionEvents();
100         // Rotate horizontal gestures to be horizontal between their first and last point.
101         // Rotate vertical gestures to be vertical between their first and last point.
102         // Sum the absolute value of every dx and dy along the gesture. Compare this with the dx
103         // and dy
104         // between the first and last point.
105         // For horizontal lines, the difference in the x direction should be small.
106         // For vertical lines, the difference in the y direction should be small.
107 
108         if (motionEvents.size() < 3) {
109             return Result.passed(0);
110         }
111 
112         List<Point> rotatedPoints;
113         if (isHorizontal()) {
114             rotatedPoints = rotateHorizontal();
115         } else {
116             rotatedPoints = rotateVertical();
117         }
118 
119         float actualDx = Math
120                 .abs(rotatedPoints.get(0).x - rotatedPoints.get(rotatedPoints.size() - 1).x);
121         float actualDy = Math
122                 .abs(rotatedPoints.get(0).y - rotatedPoints.get(rotatedPoints.size() - 1).y);
123         logDebug("Actual: (" + actualDx + "," + actualDy + ")");
124         float runningAbsDx = 0;
125         float runningAbsDy = 0;
126         float pX = 0;
127         float pY = 0;
128         boolean firstLoop = true;
129         for (Point point : rotatedPoints) {
130             if (firstLoop) {
131                 pX = point.x;
132                 pY = point.y;
133                 firstLoop = false;
134                 continue;
135             }
136             runningAbsDx += Math.abs(point.x - pX);
137             runningAbsDy += Math.abs(point.y - pY);
138             pX = point.x;
139             pY = point.y;
140             logVerbose("(x, y, runningAbsDx, runningAbsDy) - ("
141                     + pX + ", " + pY + ", " + runningAbsDx + ", " + runningAbsDy + ")");
142         }
143 
144         float devianceX = runningAbsDx - actualDx;
145         float devianceY = runningAbsDy - actualDy;
146         float distanceXIn = actualDx / getXdpi();
147         float distanceYIn = actualDy / getYdpi();
148         float totalDistanceIn = (float) Math
149                 .sqrt(distanceXIn * distanceXIn + distanceYIn * distanceYIn);
150 
151         float maxXDeviance;
152         float maxYDeviance;
153         if (actualDx > actualDy) {
154             maxXDeviance = mMaxXPrimaryDeviance * totalDistanceIn * getXdpi();
155             maxYDeviance = mMaxYSecondaryDeviance * totalDistanceIn * getYdpi();
156         } else {
157             maxXDeviance = mMaxXSecondaryDeviance * totalDistanceIn * getXdpi();
158             maxYDeviance = mMaxYPrimaryDeviance * totalDistanceIn * getYdpi();
159         }
160 
161         // These values are saved for logging reasons. {@see #getReason()}
162         mLastDevianceX = devianceX;
163         mLastDevianceY = devianceY;
164         mLastMaxXDeviance = maxXDeviance;
165         mLastMaxYDeviance = maxYDeviance;
166 
167         logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs "
168                 + "(" + maxXDeviance + "," + maxYDeviance + ")");
169         return devianceX > maxXDeviance || devianceY > maxYDeviance
170             ? falsed(0.5, getReason()) : Result.passed(0.5);
171     }
172 
getReason()173     private String getReason() {
174         return String.format(
175                 (Locale) null,
176                 "{devianceX=%f, maxDevianceX=%s, devianceY=%s, maxDevianceY=%s}",
177                 mLastDevianceX, mLastMaxXDeviance, mLastDevianceY, mLastMaxYDeviance);
178     }
179 
getAtan2LastPoint()180     private float getAtan2LastPoint() {
181         MotionEvent firstEvent = getFirstMotionEvent();
182         MotionEvent lastEvent = getLastMotionEvent();
183         float offsetX = firstEvent.getX();
184         float offsetY = firstEvent.getY();
185         float lastX = lastEvent.getX() - offsetX;
186         float lastY = lastEvent.getY() - offsetY;
187 
188         return (float) Math.atan2(lastY, lastX);
189     }
190 
rotateVertical()191     private List<Point> rotateVertical() {
192         // Calculate the angle relative to the y axis.
193         double angle = Math.PI / 2 - getAtan2LastPoint();
194         logDebug("Rotating to vertical by: " + angle);
195         return rotateMotionEvents(getRecentMotionEvents(), -angle);
196     }
197 
rotateHorizontal()198     private List<Point> rotateHorizontal() {
199         // Calculate the angle relative to the x axis.
200         double angle = getAtan2LastPoint();
201         logDebug("Rotating to horizontal by: " + angle);
202         return rotateMotionEvents(getRecentMotionEvents(), angle);
203     }
204 
rotateMotionEvents(List<MotionEvent> motionEvents, double angle)205     private List<Point> rotateMotionEvents(List<MotionEvent> motionEvents, double angle) {
206         List<Point> points = new ArrayList<>();
207         double cosAngle = Math.cos(angle);
208         double sinAngle = Math.sin(angle);
209         MotionEvent firstEvent = motionEvents.get(0);
210         float offsetX = firstEvent.getX();
211         float offsetY = firstEvent.getY();
212         for (MotionEvent motionEvent : motionEvents) {
213             float x = motionEvent.getX() - offsetX;
214             float y = motionEvent.getY() - offsetY;
215             double rotatedX = cosAngle * x + sinAngle * y + offsetX;
216             double rotatedY = -sinAngle * x + cosAngle * y + offsetY;
217             points.add(new Point((int) rotatedX, (int) rotatedY));
218         }
219 
220         MotionEvent lastEvent = motionEvents.get(motionEvents.size() - 1);
221         Point firstPoint = points.get(0);
222         Point lastPoint = points.get(points.size() - 1);
223         logDebug(
224                 "Before: (" + firstEvent.getX() + "," + firstEvent.getY() + "), ("
225                         + lastEvent.getX() + ","
226                         + lastEvent.getY() + ")");
227         logDebug(
228                 "After: (" + firstPoint.x + "," + firstPoint.y + "), (" + lastPoint.x + ","
229                         + lastPoint.y
230                         + ")");
231 
232         return points;
233     }
234 
235 }
236