| /* |
| * 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(); |
| } |
| } |
| } |