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 * 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.getEventTimeNanos(); 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.getEventTimeNanos(); 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.getEventTimeNanos()); 95 long duration = motionEvent.getEventTimeNanos() - 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_SIDE 123 || interactionType == MEDIA_SEEKBAR) { 124 return Result.passed(0); 125 } 126 127 if (mPercentNear > mPercentCoveredThreshold) { 128 Result longSwipeResult = mDistanceClassifier.isLongSwipe(); 129 return longSwipeResult.isFalse() 130 ? falsed( 131 0.5, getReason(longSwipeResult, mPercentNear, mPercentCoveredThreshold)) 132 : Result.passed(0.5); 133 } 134 135 return Result.passed(0.5); 136 } 137 getReason(Result longSwipeResult, float percentNear, float percentCoveredThreshold)138 private static String getReason(Result longSwipeResult, float percentNear, 139 float percentCoveredThreshold) { 140 return String.format( 141 (Locale) null, 142 "{percentInProximity=%f, threshold=%f, distanceClassifier=%s}", 143 percentNear, 144 percentCoveredThreshold, 145 longSwipeResult.getReason()); 146 } 147 148 /** 149 * @param near is the sensor showing the near state right now 150 * @param timeStampNs time of this event in nanoseconds 151 */ update(boolean near, long timeStampNs)152 private void update(boolean near, long timeStampNs) { 153 if (mPrevNearTimeNs != 0 && timeStampNs > mPrevNearTimeNs && mNear) { 154 mNearDurationNs += timeStampNs - mPrevNearTimeNs; 155 logDebug("Updating duration: " + mNearDurationNs); 156 } 157 158 if (near) { 159 logDebug("Set prevNearTimeNs: " + timeStampNs); 160 mPrevNearTimeNs = timeStampNs; 161 } 162 163 mNear = near; 164 } 165 } 166