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