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