blob: 00f35aa4276cb2baa2d5cfbcd6861a5b2854fa18 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.classifier.brightline;
import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED;
import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.plugins.FalsingManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* FalsingManager designed to make clear why a touch was rejected.
*/
public class BrightLineFalsingManager implements FalsingManager {
static final boolean DEBUG = false;
private static final String TAG = "FalsingManagerPlugin";
private final SensorManager mSensorManager;
private final FalsingDataProvider mDataProvider;
private boolean mSessionStarted;
private MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
private final List<FalsingClassifier> mClassifiers;
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public synchronized void onSensorChanged(SensorEvent event) {
onSensorEvent(event);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
SensorManager sensorManager) {
mDataProvider = falsingDataProvider;
mSensorManager = sensorManager;
mMetricsLogger = new MetricsLogger();
mClassifiers = new ArrayList<>();
DistanceClassifier distanceClassifier = new DistanceClassifier(mDataProvider);
ProximityClassifier proximityClassifier = new ProximityClassifier(distanceClassifier,
mDataProvider);
mClassifiers.add(new PointerCountClassifier(mDataProvider));
mClassifiers.add(new TypeClassifier(mDataProvider));
mClassifiers.add(new DiagonalClassifier(mDataProvider));
mClassifiers.add(distanceClassifier);
mClassifiers.add(proximityClassifier);
mClassifiers.add(new ZigZagClassifier(mDataProvider));
}
private void registerSensors() {
Sensor s = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (s != null) {
// This can be expensive, and doesn't need to happen on the main thread.
mBackgroundExecutor.submit(() -> {
logDebug("registering sensor listener");
mSensorManager.registerListener(
mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
});
}
}
private void unregisterSensors() {
// This can be expensive, and doesn't need to happen on the main thread.
mBackgroundExecutor.submit(() -> {
logDebug("unregistering sensor listener");
mSensorManager.unregisterListener(mSensorEventListener);
});
}
private void sessionStart() {
logDebug("Starting Session");
mSessionStarted = true;
registerSensors();
mClassifiers.forEach(FalsingClassifier::onSessionStarted);
}
private void sessionEnd() {
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
unregisterSensors();
mDataProvider.onSessionEnd();
mClassifiers.forEach(FalsingClassifier::onSessionEnded);
if (mIsFalseTouchCalls != 0) {
mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
mIsFalseTouchCalls = 0;
}
}
}
private void updateInteractionType(@Classifier.InteractionType int type) {
logDebug("InteractionType: " + type);
mClassifiers.forEach((classifier) -> classifier.setInteractionType(type));
}
@Override
public boolean isClassiferEnabled() {
return true;
}
@Override
public boolean isFalseTouch() {
boolean r = mClassifiers.stream().anyMatch(falsingClassifier -> {
boolean result = falsingClassifier.isFalseTouch();
if (result) {
logInfo(falsingClassifier.getClass().getName() + ": true");
} else {
logDebug(falsingClassifier.getClass().getName() + ": false");
}
return result;
});
logDebug("Is false touch? " + r);
return r;
}
@Override
public void onTouchEvent(MotionEvent motionEvent, int width, int height) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls.
mDataProvider.onMotionEvent(motionEvent);
mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent));
}
private void onSensorEvent(SensorEvent sensorEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls.
mClassifiers.forEach((classifier) -> classifier.onSensorEvent(sensorEvent));
}
@Override
public void onSucccessfulUnlock() {
if (mIsFalseTouchCalls != 0) {
mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
mIsFalseTouchCalls = 0;
}
}
@Override
public void onNotificationActive() {
}
@Override
public void setShowingAod(boolean showingAod) {
if (showingAod) {
sessionEnd();
} else {
sessionStart();
}
}
@Override
public void onNotificatonStartDraggingDown() {
updateInteractionType(Classifier.NOTIFICATION_DRAG_DOWN);
}
@Override
public boolean isUnlockingDisabled() {
return false;
}
@Override
public void onNotificatonStopDraggingDown() {
}
@Override
public void setNotificationExpanded() {
}
@Override
public void onQsDown() {
updateInteractionType(Classifier.QUICK_SETTINGS);
}
@Override
public void setQsExpanded(boolean b) {
}
@Override
public boolean shouldEnforceBouncer() {
return false;
}
@Override
public void onTrackingStarted(boolean secure) {
updateInteractionType(secure ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
}
@Override
public void onTrackingStopped() {
}
@Override
public void onLeftAffordanceOn() {
}
@Override
public void onCameraOn() {
}
@Override
public void onAffordanceSwipingStarted(boolean rightCorner) {
updateInteractionType(
rightCorner ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
}
@Override
public void onAffordanceSwipingAborted() {
}
@Override
public void onStartExpandingFromPulse() {
updateInteractionType(Classifier.PULSE_EXPAND);
}
@Override
public void onExpansionFromPulseStopped() {
}
@Override
public Uri reportRejectedTouch() {
return null;
}
@Override
public void onScreenOnFromTouch() {
sessionStart();
}
@Override
public boolean isReportingEnabled() {
return false;
}
@Override
public void onUnlockHintStarted() {
}
@Override
public void onCameraHintStarted() {
}
@Override
public void onLeftAffordanceHintStarted() {
}
@Override
public void onScreenTurningOn() {
sessionStart();
}
@Override
public void onScreenOff() {
sessionEnd();
}
@Override
public void onNotificatonStopDismissing() {
}
@Override
public void onNotificationDismissed() {
}
@Override
public void onNotificatonStartDismissing() {
updateInteractionType(Classifier.NOTIFICATION_DISMISS);
}
@Override
public void onNotificationDoubleTap(boolean b, float v, float v1) {
}
@Override
public void onBouncerShown() {
}
@Override
public void onBouncerHidden() {
}
@Override
public void dump(PrintWriter printWriter) {
}
static void logDebug(String msg) {
logDebug(msg, null);
}
static void logDebug(String msg, Throwable throwable) {
if (DEBUG) {
Log.d(TAG, msg, throwable);
}
}
static void logInfo(String msg) {
Log.i(TAG, msg);
}
static void logError(String msg) {
Log.e(TAG, msg);
}
}