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