blob: 5b1082acd59fb854a234b084d25965da3806cd67 [file] [log] [blame]
/*
* Copyright (C) 2020 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;
import static com.android.systemui.dock.DockManager.DockEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
import android.view.MotionEvent;
import androidx.annotation.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
import java.util.Collections;
import javax.inject.Inject;
@SysUISingleton
class FalsingCollectorImpl implements FalsingCollector {
private static final String TAG = "FalsingCollector";
private static final String PROXIMITY_SENSOR_TAG = "FalsingCollector";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long GESTURE_PROCESSING_DELAY_MS = 100;
private final FalsingDataProvider mFalsingDataProvider;
private final FalsingManager mFalsingManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final HistoryTracker mHistoryTracker;
private final ProximitySensor mProximitySensor;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final BatteryController mBatteryController;
private final DockManager mDockManager;
private final DelayableExecutor mMainExecutor;
private final JavaAdapter mJavaAdapter;
private final SystemClock mSystemClock;
private final Lazy<SelectedUserInteractor> mUserInteractor;
private int mState;
private boolean mShowingAod;
private boolean mScreenOn;
private boolean mSessionStarted;
private MotionEvent mPendingDownEvent;
private boolean mAvoidGesture;
private final ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent;
private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStateChanged(int newState) {
logDebug("StatusBarState=" + StatusBarState.toString(newState));
mState = newState;
updateSessionActive();
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
public void onBiometricAuthenticated(int userId,
BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
if (userId == mUserInteractor.get().getSelectedUserId()
&& biometricSourceType == BiometricSourceType.FACE) {
mFalsingDataProvider.setJustUnlockedWithFace(true);
}
}
};
private final BatteryStateChangeCallback mBatteryListener = new BatteryStateChangeCallback() {
@Override
public void onWirelessChargingChanged(boolean isWirelessCharging) {
if (isWirelessCharging || mDockManager.isDocked()) {
mProximitySensor.pause();
} else {
mProximitySensor.resume();
}
}
};
private final DockEventListener mDockEventListener = new DockEventListener() {
@Override
public void onEvent(int event) {
if (event == DockManager.STATE_NONE && !mBatteryController.isWirelessCharging()) {
mProximitySensor.resume();
} else {
mProximitySensor.pause();
}
}
};
@Inject
FalsingCollectorImpl(
FalsingDataProvider falsingDataProvider,
FalsingManager falsingManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
HistoryTracker historyTracker,
ProximitySensor proximitySensor,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<ShadeInteractor> shadeInteractorLazy,
BatteryController batteryController,
DockManager dockManager,
@Main DelayableExecutor mainExecutor,
JavaAdapter javaAdapter,
SystemClock systemClock,
Lazy<SelectedUserInteractor> userInteractor) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mHistoryTracker = historyTracker;
mProximitySensor = proximitySensor;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mShadeInteractorLazy = shadeInteractorLazy;
mBatteryController = batteryController;
mDockManager = dockManager;
mMainExecutor = mainExecutor;
mJavaAdapter = javaAdapter;
mSystemClock = systemClock;
mUserInteractor = userInteractor;
}
@Override
public void init() {
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
mShadeInteractorLazy.get().isQsExpanded(),
this::onQsExpansionChanged
);
mBatteryController.addCallback(mBatteryListener);
mDockManager.addListener(mDockEventListener);
}
@Override
public void onSuccessfulUnlock() {
logDebug("REAL: onSuccessfulUnlock");
mFalsingManager.onSuccessfulUnlock();
sessionEnd();
}
@Override
public void setShowingAod(boolean showingAod) {
logDebug("REAL: setShowingAod(" + showingAod + ")");
mShowingAod = showingAod;
updateSessionActive();
}
@VisibleForTesting
void onQsExpansionChanged(Boolean expanded) {
logDebug("REAL: onQsExpansionChanged(" + expanded + ")");
if (expanded) {
unregisterSensors();
} else if (mSessionStarted) {
registerSensors();
}
}
@Override
public boolean shouldEnforceBouncer() {
return false;
}
@Override
public void onScreenOnFromTouch() {
logDebug("REAL: onScreenOnFromTouch");
onScreenTurningOn();
}
@Override
public boolean isReportingEnabled() {
return false;
}
@Override
public void onScreenTurningOn() {
logDebug("REAL: onScreenTurningOn");
mScreenOn = true;
updateSessionActive();
}
@Override
public void onScreenOff() {
logDebug("REAL: onScreenOff");
mScreenOn = false;
updateSessionActive();
}
@Override
public void onBouncerShown() {
logDebug("REAL: onBouncerShown");
unregisterSensors();
}
@Override
public void onBouncerHidden() {
logDebug("REAL: onBouncerHidden");
if (mSessionStarted) {
registerSensors();
}
}
@Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
avoidGesture();
return;
}
if (ev.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
return;
}
// We delay processing down events to see if another component wants to process them.
// If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
// will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
// avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
// any other events are processed, otherwise the whole gesture will be recorded.
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Make a copy of ev, since it will be recycled after we exit this method.
mPendingDownEvent = MotionEvent.obtain(ev);
mAvoidGesture = false;
} else if (!mAvoidGesture) {
if (mPendingDownEvent != null) {
mFalsingDataProvider.onMotionEvent(mPendingDownEvent);
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
mFalsingDataProvider.onMotionEvent(ev);
}
}
@Override
public void onMotionEventComplete() {
logDebug("REAL: onMotionEventComplete");
// We must delay processing the completion because of the way Android handles click events.
// It generally delays executing them immediately, instead choosing to give the UI a chance
// to respond to touch events before acknowledging the click. As such, we must also delay,
// giving click handlers a chance to analyze it.
// You might think we could do something clever to remove this delay - adding non-committed
// results that can later be changed - but this won't help. Calling the code
// below can eventually end up in a "Falsing Event" being fired. If we remove the delay
// here, we would still have to add the delay to the event, but we'd also have to make all
// the intervening code more complicated in the process. This is the simplest insertion
// point for the delay.
mMainExecutor.executeDelayed(
mFalsingDataProvider::onMotionEventComplete, GESTURE_PROCESSING_DELAY_MS);
}
@Override
public void avoidGesture() {
logDebug("REAL: avoidGesture");
mAvoidGesture = true;
if (mPendingDownEvent != null) {
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
}
@Override
public void cleanup() {
logDebug("REAL: cleanup");
unregisterSensors();
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mBatteryController.removeCallback(mBatteryListener);
mDockManager.removeListener(mDockEventListener);
}
@Override
public void updateFalseConfidence(FalsingClassifier.Result result) {
logDebug("REAL: updateFalseConfidence(" + result.isFalse() + ")");
mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
}
@Override
public void onA11yAction() {
logDebug("REAL: onA11yAction");
if (mPendingDownEvent != null) {
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
mFalsingDataProvider.onA11yAction();
}
private boolean shouldSessionBeActive() {
return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
}
private void updateSessionActive() {
if (shouldSessionBeActive()) {
sessionStart();
} else {
sessionEnd();
}
}
private void sessionStart() {
if (!mSessionStarted && shouldSessionBeActive()) {
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
private void sessionEnd() {
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
private void registerSensors() {
mProximitySensor.register(mSensorEventListener);
}
private void unregisterSensors() {
mProximitySensor.unregister(mSensorEventListener);
}
private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls.
mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
}
static void logDebug(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
}
}
private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
private final ThresholdSensorEvent mThresholdSensorEvent;
ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
mThresholdSensorEvent = thresholdSensorEvent;
}
@Override
public boolean getCovered() {
return mThresholdSensorEvent.getBelow();
}
@Override
public long getTimestampNs() {
return mThresholdSensorEvent.getTimestampNs();
}
}
}