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