/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_X_PRIMARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.graphics.Point; import android.provider.DeviceConfig; import android.view.MotionEvent; import com.android.systemui.util.DeviceConfigProxy; import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.inject.Inject; /** * Penalizes gestures that change direction in either the x or y too much. */ class ZigZagClassifier extends FalsingClassifier { // Define how far one can move back and forth over one inch of travel before being falsed. // `PRIMARY` defines how far one can deviate in the primary direction of travel. I.e. if you're // swiping vertically, you shouldn't have a lot of zig zag in the vertical direction. Since // most swipes will follow somewhat of a 'C' or 'S' shape, we allow more deviance along the // `SECONDARY` axis. private static final float MAX_X_PRIMARY_DEVIANCE = .05f; private static final float MAX_Y_PRIMARY_DEVIANCE = .15f; private static final float MAX_X_SECONDARY_DEVIANCE = .4f; private static final float MAX_Y_SECONDARY_DEVIANCE = .3f; private final float mMaxXPrimaryDeviance; private final float mMaxYPrimaryDeviance; private final float mMaxXSecondaryDeviance; private final float mMaxYSecondaryDeviance; private float mLastDevianceX; private float mLastDevianceY; private float mLastMaxXDeviance; private float mLastMaxYDeviance; @Inject ZigZagClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) { super(dataProvider); mMaxXPrimaryDeviance = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_ZIGZAG_X_PRIMARY_DEVIANCE, MAX_X_PRIMARY_DEVIANCE); mMaxYPrimaryDeviance = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE, MAX_Y_PRIMARY_DEVIANCE); mMaxXSecondaryDeviance = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE, MAX_X_SECONDARY_DEVIANCE); mMaxYSecondaryDeviance = deviceConfigProxy.getFloat( DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE, MAX_Y_SECONDARY_DEVIANCE); } @Override Result calculateFalsingResult( @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { if (interactionType == BRIGHTNESS_SLIDER || interactionType == MEDIA_SEEKBAR || interactionType == SHADE_DRAG) { return Result.passed(0); } List motionEvents = getRecentMotionEvents(); // Rotate horizontal gestures to be horizontal between their first and last point. // Rotate vertical gestures to be vertical between their first and last point. // Sum the absolute value of every dx and dy along the gesture. Compare this with the dx // and dy // between the first and last point. // For horizontal lines, the difference in the x direction should be small. // For vertical lines, the difference in the y direction should be small. if (motionEvents.size() < 3) { return Result.passed(0); } List rotatedPoints; if (isHorizontal()) { rotatedPoints = rotateHorizontal(); } else { rotatedPoints = rotateVertical(); } float actualDx = Math .abs(rotatedPoints.get(0).x - rotatedPoints.get(rotatedPoints.size() - 1).x); float actualDy = Math .abs(rotatedPoints.get(0).y - rotatedPoints.get(rotatedPoints.size() - 1).y); logDebug("Actual: (" + actualDx + "," + actualDy + ")"); float runningAbsDx = 0; float runningAbsDy = 0; float pX = 0; float pY = 0; boolean firstLoop = true; for (Point point : rotatedPoints) { if (firstLoop) { pX = point.x; pY = point.y; firstLoop = false; continue; } runningAbsDx += Math.abs(point.x - pX); runningAbsDy += Math.abs(point.y - pY); pX = point.x; pY = point.y; logVerbose("(x, y, runningAbsDx, runningAbsDy) - (" + pX + ", " + pY + ", " + runningAbsDx + ", " + runningAbsDy + ")"); } float devianceX = runningAbsDx - actualDx; float devianceY = runningAbsDy - actualDy; float distanceXIn = actualDx / getXdpi(); float distanceYIn = actualDy / getYdpi(); float totalDistanceIn = (float) Math .sqrt(distanceXIn * distanceXIn + distanceYIn * distanceYIn); float maxXDeviance; float maxYDeviance; if (actualDx > actualDy) { maxXDeviance = mMaxXPrimaryDeviance * totalDistanceIn * getXdpi(); maxYDeviance = mMaxYSecondaryDeviance * totalDistanceIn * getYdpi(); } else { maxXDeviance = mMaxXSecondaryDeviance * totalDistanceIn * getXdpi(); maxYDeviance = mMaxYPrimaryDeviance * totalDistanceIn * getYdpi(); } // These values are saved for logging reasons. {@see #getReason()} mLastDevianceX = devianceX; mLastDevianceY = devianceY; mLastMaxXDeviance = maxXDeviance; mLastMaxYDeviance = maxYDeviance; logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs " + "(" + maxXDeviance + "," + maxYDeviance + ")"); return devianceX > maxXDeviance || devianceY > maxYDeviance ? falsed(0.5, getReason()) : Result.passed(0.5); } private String getReason() { return String.format( (Locale) null, "{devianceX=%f, maxDevianceX=%s, devianceY=%s, maxDevianceY=%s}", mLastDevianceX, mLastMaxXDeviance, mLastDevianceY, mLastMaxYDeviance); } private float getAtan2LastPoint() { MotionEvent firstEvent = getFirstMotionEvent(); MotionEvent lastEvent = getLastMotionEvent(); float offsetX = firstEvent.getX(); float offsetY = firstEvent.getY(); float lastX = lastEvent.getX() - offsetX; float lastY = lastEvent.getY() - offsetY; return (float) Math.atan2(lastY, lastX); } private List rotateVertical() { // Calculate the angle relative to the y axis. double angle = Math.PI / 2 - getAtan2LastPoint(); logDebug("Rotating to vertical by: " + angle); return rotateMotionEvents(getRecentMotionEvents(), -angle); } private List rotateHorizontal() { // Calculate the angle relative to the x axis. double angle = getAtan2LastPoint(); logDebug("Rotating to horizontal by: " + angle); return rotateMotionEvents(getRecentMotionEvents(), angle); } private List rotateMotionEvents(List motionEvents, double angle) { List points = new ArrayList<>(); double cosAngle = Math.cos(angle); double sinAngle = Math.sin(angle); MotionEvent firstEvent = motionEvents.get(0); float offsetX = firstEvent.getX(); float offsetY = firstEvent.getY(); for (MotionEvent motionEvent : motionEvents) { float x = motionEvent.getX() - offsetX; float y = motionEvent.getY() - offsetY; double rotatedX = cosAngle * x + sinAngle * y + offsetX; double rotatedY = -sinAngle * x + cosAngle * y + offsetY; points.add(new Point((int) rotatedX, (int) rotatedY)); } MotionEvent lastEvent = motionEvents.get(motionEvents.size() - 1); Point firstPoint = points.get(0); Point lastPoint = points.get(points.size() - 1); logDebug( "Before: (" + firstEvent.getX() + "," + firstEvent.getY() + "), (" + lastEvent.getX() + "," + lastEvent.getY() + ")"); logDebug( "After: (" + firstPoint.x + "," + firstPoint.y + "), (" + lastPoint.x + "," + lastPoint.y + ")"); return points; } }