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