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_PROXIMITY_PERCENT_COVERED_THRESHOLD;
20 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
21 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
22 import static com.android.systemui.classifier.Classifier.QS_SWIPE;
23 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
24 
25 import android.provider.DeviceConfig;
26 import android.view.MotionEvent;
27 
28 import com.android.systemui.plugins.FalsingManager;
29 import com.android.systemui.util.DeviceConfigProxy;
30 
31 import java.util.Locale;
32 
33 import javax.inject.Inject;
34 
35 
36 /**
37  * False touch if proximity sensor is covered for more than a certain percentage of the gesture.
38  *
39  * This classifier is essentially a no-op for QUICK_SETTINGS, as we assume the sensor may be
40  * covered when swiping from the top.
41  */
42 class ProximityClassifier extends FalsingClassifier {
43 
44     private static final float PERCENT_COVERED_THRESHOLD = 0.1f;
45     private final DistanceClassifier mDistanceClassifier;
46     private final float mPercentCoveredThreshold;
47 
48     private boolean mNear;
49     private long mGestureStartTimeNs;
50     private long mPrevNearTimeNs;
51     private long mNearDurationNs;
52     private float mPercentNear;
53 
54     @Inject
ProximityClassifier(DistanceClassifier distanceClassifier, FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy)55     ProximityClassifier(DistanceClassifier distanceClassifier,
56             FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
57         super(dataProvider);
58         mDistanceClassifier = distanceClassifier;
59 
60         mPercentCoveredThreshold = deviceConfigProxy.getFloat(
61                 DeviceConfig.NAMESPACE_SYSTEMUI,
62                 BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD,
63                 PERCENT_COVERED_THRESHOLD);
64     }
65 
66     @Override
onSessionStarted()67     void onSessionStarted() {
68         mPrevNearTimeNs = 0;
69         mPercentNear = 0;
70     }
71 
72     @Override
onSessionEnded()73     void onSessionEnded() {
74         mPrevNearTimeNs = 0;
75         mPercentNear = 0;
76     }
77 
78     @Override
onTouchEvent(MotionEvent motionEvent)79     public void onTouchEvent(MotionEvent motionEvent) {
80         int action = motionEvent.getActionMasked();
81 
82         if (action == MotionEvent.ACTION_DOWN) {
83             mGestureStartTimeNs = motionEvent.getEventTimeNano();
84             if (mPrevNearTimeNs > 0) {
85                 // We only care about if the proximity sensor is triggered while a move event is
86                 // happening.
87                 mPrevNearTimeNs = motionEvent.getEventTimeNano();
88             }
89             logDebug("Gesture start time: " + mGestureStartTimeNs);
90             mNearDurationNs = 0;
91         }
92 
93         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
94             update(mNear, motionEvent.getEventTimeNano());
95             long duration = motionEvent.getEventTimeNano() - mGestureStartTimeNs;
96 
97             logDebug("Gesture duration, Proximity duration: " + duration + ", " + mNearDurationNs);
98 
99             if (duration == 0) {
100                 mPercentNear = mNear ? 1.0f : 0.0f;
101             } else {
102                 mPercentNear = (float) mNearDurationNs / (float) duration;
103             }
104         }
105 
106     }
107 
108     @Override
onProximityEvent( FalsingManager.ProximityEvent proximityEvent)109     public void onProximityEvent(
110             FalsingManager.ProximityEvent proximityEvent) {
111         boolean covered = proximityEvent.getCovered();
112         long timestampNs = proximityEvent.getTimestampNs();
113         logDebug("Sensor is: " + covered + " at time " + timestampNs);
114         update(covered, timestampNs);
115     }
116 
117     @Override
calculateFalsingResult( @lassifier.InteractionType int interactionType, double historyBelief, double historyConfidence)118     Result calculateFalsingResult(
119             @Classifier.InteractionType int interactionType,
120             double historyBelief, double historyConfidence) {
121         if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER
122                 || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE) {
123             return Result.passed(0);
124         }
125 
126         if (mPercentNear > mPercentCoveredThreshold) {
127             Result longSwipeResult = mDistanceClassifier.isLongSwipe();
128             return longSwipeResult.isFalse()
129                     ? falsed(
130                             0.5, getReason(longSwipeResult, mPercentNear, mPercentCoveredThreshold))
131                     : Result.passed(0.5);
132         }
133 
134         return Result.passed(0.5);
135     }
136 
getReason(Result longSwipeResult, float percentNear, float percentCoveredThreshold)137     private static String getReason(Result longSwipeResult, float percentNear,
138             float percentCoveredThreshold) {
139         return String.format(
140                 (Locale) null,
141                 "{percentInProximity=%f, threshold=%f, distanceClassifier=%s}",
142                 percentNear,
143                 percentCoveredThreshold,
144                 longSwipeResult.getReason());
145     }
146 
147     /**
148      * @param near        is the sensor showing the near state right now
149      * @param timeStampNs time of this event in nanoseconds
150      */
update(boolean near, long timeStampNs)151     private void update(boolean near, long timeStampNs) {
152         if (mPrevNearTimeNs != 0 && timeStampNs > mPrevNearTimeNs && mNear) {
153             mNearDurationNs += timeStampNs - mPrevNearTimeNs;
154             logDebug("Updating duration: " + mNearDurationNs);
155         }
156 
157         if (near) {
158             logDebug("Set prevNearTimeNs: " + timeStampNs);
159             mPrevNearTimeNs = timeStampNs;
160         }
161 
162         mNear = near;
163     }
164 }
165