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