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_DISTANCE_HORIZONTAL_FLING_THRESHOLD_IN; 20 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_SWIPE_THRESHOLD_IN; 21 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_SCREEN_FRACTION_MAX_DISTANCE; 22 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE; 23 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN; 24 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN; 25 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; 26 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; 27 import static com.android.systemui.classifier.Classifier.SHADE_DRAG; 28 29 import android.provider.DeviceConfig; 30 import android.view.MotionEvent; 31 import android.view.VelocityTracker; 32 33 import com.android.systemui.util.DeviceConfigProxy; 34 35 import java.util.List; 36 import java.util.Locale; 37 38 import javax.inject.Inject; 39 40 /** 41 * Ensure that the swipe + momentum covers a minimum distance. 42 */ 43 class DistanceClassifier extends FalsingClassifier { 44 45 private static final float HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN = 1; 46 private static final float VERTICAL_FLING_THRESHOLD_DISTANCE_IN = 1.5f; 47 private static final float HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN = 3; 48 private static final float VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN = 3; 49 private static final float VELOCITY_TO_DISTANCE = 30f; 50 private static final float SCREEN_FRACTION_MAX_DISTANCE = 0.8f; 51 52 private final float mVerticalFlingThresholdPx; 53 private final float mHorizontalFlingThresholdPx; 54 private final float mVerticalSwipeThresholdPx; 55 private final float mHorizontalSwipeThresholdPx; 56 private final float mVelocityToDistanceMultiplier; 57 58 private boolean mDistanceDirty; 59 private DistanceVectors mCachedDistance; 60 61 @Inject DistanceClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy)62 DistanceClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) { 63 super(dataProvider); 64 65 mVelocityToDistanceMultiplier = deviceConfigProxy.getFloat( 66 DeviceConfig.NAMESPACE_SYSTEMUI, 67 BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE, 68 VELOCITY_TO_DISTANCE); 69 70 float horizontalFlingThresholdIn = deviceConfigProxy.getFloat( 71 DeviceConfig.NAMESPACE_SYSTEMUI, 72 BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_FLING_THRESHOLD_IN, 73 HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN); 74 75 float verticalFlingThresholdIn = deviceConfigProxy.getFloat( 76 DeviceConfig.NAMESPACE_SYSTEMUI, 77 BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN, 78 VERTICAL_FLING_THRESHOLD_DISTANCE_IN); 79 80 float horizontalSwipeThresholdIn = deviceConfigProxy.getFloat( 81 DeviceConfig.NAMESPACE_SYSTEMUI, 82 BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_SWIPE_THRESHOLD_IN, 83 HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN); 84 85 float verticalSwipeThresholdIn = deviceConfigProxy.getFloat( 86 DeviceConfig.NAMESPACE_SYSTEMUI, 87 BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN, 88 VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN); 89 90 float screenFractionMaxDistance = deviceConfigProxy.getFloat( 91 DeviceConfig.NAMESPACE_SYSTEMUI, 92 BRIGHTLINE_FALSING_DISTANCE_SCREEN_FRACTION_MAX_DISTANCE, 93 SCREEN_FRACTION_MAX_DISTANCE); 94 95 mHorizontalFlingThresholdPx = Math 96 .min(getWidthPixels() * screenFractionMaxDistance, 97 horizontalFlingThresholdIn * getXdpi()); 98 mVerticalFlingThresholdPx = Math 99 .min(getHeightPixels() * screenFractionMaxDistance, 100 verticalFlingThresholdIn * getYdpi()); 101 mHorizontalSwipeThresholdPx = Math 102 .min(getWidthPixels() * screenFractionMaxDistance, 103 horizontalSwipeThresholdIn * getXdpi()); 104 mVerticalSwipeThresholdPx = Math 105 .min(getHeightPixels() * screenFractionMaxDistance, 106 verticalSwipeThresholdIn * getYdpi()); 107 mDistanceDirty = true; 108 } 109 getDistances()110 private DistanceVectors getDistances() { 111 if (mDistanceDirty) { 112 mCachedDistance = calculateDistances(); 113 mDistanceDirty = false; 114 } 115 116 return mCachedDistance; 117 } 118 calculateDistances()119 private DistanceVectors calculateDistances() { 120 // This code assumes that there will be no missed DOWN or UP events. 121 List<MotionEvent> motionEvents = getRecentMotionEvents(); 122 123 if (motionEvents.size() < 3) { 124 logDebug("Only " + motionEvents.size() + " motion events recorded."); 125 return new DistanceVectors(0, 0, 0, 0); 126 } 127 128 VelocityTracker velocityTracker = VelocityTracker.obtain(); 129 for (MotionEvent motionEvent : motionEvents) { 130 velocityTracker.addMovement(motionEvent); 131 } 132 velocityTracker.computeCurrentVelocity(1); 133 134 float vX = velocityTracker.getXVelocity(); 135 float vY = velocityTracker.getYVelocity(); 136 137 velocityTracker.recycle(); 138 139 float dX = getLastMotionEvent().getX() - getFirstMotionEvent().getX(); 140 float dY = getLastMotionEvent().getY() - getFirstMotionEvent().getY(); 141 142 return new DistanceVectors(dX, dY, vX, vY); 143 } 144 145 @Override onTouchEvent(MotionEvent motionEvent)146 public void onTouchEvent(MotionEvent motionEvent) { 147 mDistanceDirty = true; 148 } 149 150 @Override calculateFalsingResult( @lassifier.InteractionType int interactionType, double historyBelief, double historyConfidence)151 Result calculateFalsingResult( 152 @Classifier.InteractionType int interactionType, 153 double historyBelief, double historyConfidence) { 154 if (interactionType == BRIGHTNESS_SLIDER 155 || interactionType == SHADE_DRAG 156 || interactionType == QS_COLLAPSE 157 || interactionType == Classifier.UDFPS_AUTHENTICATION 158 || interactionType == Classifier.LOCK_ICON 159 || interactionType == Classifier.QS_SWIPE) { 160 return Result.passed(0); 161 } 162 163 return !getPassedFlingThreshold() ? falsed(0.5, getReason()) : Result.passed(0.5); 164 } 165 getReason()166 String getReason() { 167 DistanceVectors distanceVectors = getDistances(); 168 169 return String.format( 170 (Locale) null, 171 "{distanceVectors=%s, isHorizontal=%s, velocityToDistanceMultiplier=%f, " 172 + "horizontalFlingThreshold=%f, verticalFlingThreshold=%f, " 173 + "horizontalSwipeThreshold=%f, verticalSwipeThreshold=%s}", 174 distanceVectors, 175 isHorizontal(), 176 mVelocityToDistanceMultiplier, 177 mHorizontalFlingThresholdPx, 178 mVerticalFlingThresholdPx, 179 mHorizontalSwipeThresholdPx, 180 mVerticalSwipeThresholdPx); 181 } 182 isLongSwipe()183 Result isLongSwipe() { 184 boolean longSwipe = getPassedDistanceThreshold(); 185 logDebug("Is longSwipe? " + longSwipe); 186 return longSwipe ? Result.passed(0.5) : falsed(0.5, getReason()); 187 } 188 getPassedDistanceThreshold()189 private boolean getPassedDistanceThreshold() { 190 DistanceVectors distanceVectors = getDistances(); 191 if (isHorizontal()) { 192 logDebug("Horizontal swipe distance: " + Math.abs(distanceVectors.mDx)); 193 logDebug("Threshold: " + mHorizontalSwipeThresholdPx); 194 195 return Math.abs(distanceVectors.mDx) >= mHorizontalSwipeThresholdPx; 196 } 197 198 logDebug("Vertical swipe distance: " + Math.abs(distanceVectors.mDy)); 199 logDebug("Threshold: " + mVerticalSwipeThresholdPx); 200 return Math.abs(distanceVectors.mDy) >= mVerticalSwipeThresholdPx; 201 } 202 getPassedFlingThreshold()203 private boolean getPassedFlingThreshold() { 204 DistanceVectors distanceVectors = getDistances(); 205 206 float dX = distanceVectors.mDx + distanceVectors.mVx * mVelocityToDistanceMultiplier; 207 float dY = distanceVectors.mDy + distanceVectors.mVy * mVelocityToDistanceMultiplier; 208 209 if (isHorizontal()) { 210 logDebug("Horizontal swipe and fling distance: " + distanceVectors.mDx + ", " 211 + distanceVectors.mVx * mVelocityToDistanceMultiplier); 212 logDebug("Threshold: " + mHorizontalFlingThresholdPx); 213 return Math.abs(dX) >= mHorizontalFlingThresholdPx; 214 } 215 216 logDebug("Vertical swipe and fling distance: " + distanceVectors.mDy + ", " 217 + distanceVectors.mVy * mVelocityToDistanceMultiplier); 218 logDebug("Threshold: " + mVerticalFlingThresholdPx); 219 return Math.abs(dY) >= mVerticalFlingThresholdPx; 220 } 221 222 private class DistanceVectors { 223 final float mDx; 224 final float mDy; 225 private final float mVx; 226 private final float mVy; 227 DistanceVectors(float dX, float dY, float vX, float vY)228 DistanceVectors(float dX, float dY, float vX, float vY) { 229 this.mDx = dX; 230 this.mDy = dY; 231 this.mVx = vX; 232 this.mVy = vY; 233 } 234 235 @Override toString()236 public String toString() { 237 return String.format((Locale) null, "{dx=%f, vx=%f, dy=%f, vy=%f}", mDx, mVx, mDy, mVy); 238 } 239 } 240 } 241