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