Home | History | Annotate | Download | only in classifier
      1 /*
      2  * Copyright (C) 2019 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 android.content.Context;
     20 import android.database.ContentObserver;
     21 import android.hardware.Sensor;
     22 import android.hardware.SensorEvent;
     23 import android.hardware.SensorEventListener;
     24 import android.hardware.SensorManager;
     25 import android.hardware.biometrics.BiometricSourceType;
     26 import android.net.Uri;
     27 import android.os.Handler;
     28 import android.os.Looper;
     29 import android.os.PowerManager;
     30 import android.os.UserHandle;
     31 import android.provider.Settings;
     32 import android.view.InputDevice;
     33 import android.view.MotionEvent;
     34 import android.view.accessibility.AccessibilityManager;
     35 
     36 import com.android.internal.logging.MetricsLogger;
     37 import com.android.keyguard.KeyguardUpdateMonitor;
     38 import com.android.keyguard.KeyguardUpdateMonitorCallback;
     39 import com.android.systemui.Dependency;
     40 import com.android.systemui.UiOffloadThread;
     41 import com.android.systemui.analytics.DataCollector;
     42 import com.android.systemui.plugins.FalsingManager;
     43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
     44 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
     45 import com.android.systemui.statusbar.StatusBarState;
     46 import com.android.systemui.util.AsyncSensorManager;
     47 
     48 import java.io.PrintWriter;
     49 
     50 /**
     51  * When the phone is locked, listens to touch, sensor and phone events and sends them to
     52  * DataCollector and HumanInteractionClassifier.
     53  *
     54  * It does not collect touch events when the bouncer shows up.
     55  */
     56 public class FalsingManagerImpl implements FalsingManager {
     57     private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
     58 
     59     private static final int[] CLASSIFIER_SENSORS = new int[] {
     60             Sensor.TYPE_PROXIMITY,
     61     };
     62 
     63     private static final int[] COLLECTOR_SENSORS = new int[] {
     64             Sensor.TYPE_ACCELEROMETER,
     65             Sensor.TYPE_GYROSCOPE,
     66             Sensor.TYPE_PROXIMITY,
     67             Sensor.TYPE_LIGHT,
     68             Sensor.TYPE_ROTATION_VECTOR,
     69     };
     70     private static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
     71     private static final String FALSING_SUCCESS = "falsing_success_after_attempts";
     72 
     73     private final Handler mHandler = new Handler(Looper.getMainLooper());
     74     private final Context mContext;
     75 
     76     private final SensorManager mSensorManager;
     77     private final DataCollector mDataCollector;
     78     private final HumanInteractionClassifier mHumanInteractionClassifier;
     79     private final AccessibilityManager mAccessibilityManager;
     80     private final UiOffloadThread mUiOffloadThread;
     81 
     82     private boolean mEnforceBouncer = false;
     83     private boolean mBouncerOn = false;
     84     private boolean mBouncerOffOnDown = false;
     85     private boolean mSessionActive = false;
     86     private boolean mIsTouchScreen = true;
     87     private boolean mJustUnlockedWithFace = false;
     88     private int mState = StatusBarState.SHADE;
     89     private boolean mScreenOn;
     90     private boolean mShowingAod;
     91     private Runnable mPendingWtf;
     92     private int mIsFalseTouchCalls;
     93     private MetricsLogger mMetricsLogger;
     94 
     95     private SensorEventListener mSensorEventListener = new SensorEventListener() {
     96         @Override
     97         public synchronized void onSensorChanged(SensorEvent event) {
     98             mDataCollector.onSensorChanged(event);
     99             mHumanInteractionClassifier.onSensorChanged(event);
    100         }
    101 
    102         @Override
    103         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    104             mDataCollector.onAccuracyChanged(sensor, accuracy);
    105         }
    106     };
    107 
    108     public StateListener mStatusBarStateListener = new StateListener() {
    109         @Override
    110         public void onStateChanged(int newState) {
    111             if (FalsingLog.ENABLED) {
    112                 FalsingLog.i("setStatusBarState", new StringBuilder()
    113                         .append("from=").append(StatusBarState.toShortString(mState))
    114                         .append(" to=").append(StatusBarState.toShortString(newState))
    115                         .toString());
    116             }
    117             mState = newState;
    118             updateSessionActive();
    119         }
    120     };
    121 
    122     protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
    123         @Override
    124         public void onChange(boolean selfChange) {
    125             updateConfiguration();
    126         }
    127     };
    128     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
    129             new KeyguardUpdateMonitorCallback() {
    130                 @Override
    131                 public void onBiometricAuthenticated(int userId,
    132                         BiometricSourceType biometricSourceType) {
    133                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
    134                             && biometricSourceType == BiometricSourceType.FACE) {
    135                         mJustUnlockedWithFace = true;
    136                     }
    137                 }
    138             };
    139 
    140     FalsingManagerImpl(Context context) {
    141         mContext = context;
    142         mSensorManager = Dependency.get(AsyncSensorManager.class);
    143         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
    144         mDataCollector = DataCollector.getInstance(mContext);
    145         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
    146         mUiOffloadThread = Dependency.get(UiOffloadThread.class);
    147         mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
    148         mMetricsLogger = new MetricsLogger();
    149 
    150         mContext.getContentResolver().registerContentObserver(
    151                 Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
    152                 mSettingsObserver,
    153                 UserHandle.USER_ALL);
    154 
    155         updateConfiguration();
    156         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
    157         KeyguardUpdateMonitor.getInstance(context).registerCallback(mKeyguardUpdateCallback);
    158     }
    159 
    160     private void updateConfiguration() {
    161         mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
    162                 ENFORCE_BOUNCER, 0);
    163     }
    164 
    165     private boolean shouldSessionBeActive() {
    166         if (FalsingLog.ENABLED && FalsingLog.VERBOSE) {
    167             FalsingLog.v("shouldBeActive", new StringBuilder()
    168                     .append("enabled=").append(isEnabled() ? 1 : 0)
    169                     .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
    170                     .append(" mState=").append(StatusBarState.toShortString(mState))
    171                     .append(" mShowingAod=").append(mShowingAod ? 1 : 0)
    172                     .toString()
    173             );
    174         }
    175         return isEnabled() && mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
    176     }
    177 
    178     private boolean sessionEntrypoint() {
    179         if (!mSessionActive && shouldSessionBeActive()) {
    180             onSessionStart();
    181             return true;
    182         }
    183         return false;
    184     }
    185 
    186     private void sessionExitpoint(boolean force) {
    187         if (mSessionActive && (force || !shouldSessionBeActive())) {
    188             mSessionActive = false;
    189             if (mIsFalseTouchCalls != 0) {
    190                 if (FalsingLog.ENABLED) {
    191                     FalsingLog.i(
    192                             "isFalseTouchCalls", "Calls before failure: " + mIsFalseTouchCalls);
    193                 }
    194                 mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
    195                 mIsFalseTouchCalls = 0;
    196             }
    197 
    198             // This can be expensive, and doesn't need to happen on the main thread.
    199             mUiOffloadThread.submit(() -> {
    200                 mSensorManager.unregisterListener(mSensorEventListener);
    201             });
    202         }
    203     }
    204 
    205     public void updateSessionActive() {
    206         if (shouldSessionBeActive()) {
    207             sessionEntrypoint();
    208         } else {
    209             sessionExitpoint(false /* force */);
    210         }
    211     }
    212 
    213     private void onSessionStart() {
    214         if (FalsingLog.ENABLED) {
    215             FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassiferEnabled());
    216             clearPendingWtf();
    217         }
    218         mBouncerOn = false;
    219         mSessionActive = true;
    220         mJustUnlockedWithFace = false;
    221         mIsFalseTouchCalls = 0;
    222 
    223         if (mHumanInteractionClassifier.isEnabled()) {
    224             registerSensors(CLASSIFIER_SENSORS);
    225         }
    226         if (mDataCollector.isEnabledFull()) {
    227             registerSensors(COLLECTOR_SENSORS);
    228         }
    229         if (mDataCollector.isEnabled()) {
    230             mDataCollector.onFalsingSessionStarted();
    231         }
    232     }
    233 
    234     private void registerSensors(int [] sensors) {
    235         for (int sensorType : sensors) {
    236             Sensor s = mSensorManager.getDefaultSensor(sensorType);
    237             if (s != null) {
    238 
    239                 // This can be expensive, and doesn't need to happen on the main thread.
    240                 mUiOffloadThread.submit(() -> {
    241                     mSensorManager.registerListener(
    242                             mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
    243                 });
    244             }
    245         }
    246     }
    247 
    248     public boolean isClassiferEnabled() {
    249         return mHumanInteractionClassifier.isEnabled();
    250     }
    251 
    252     private boolean isEnabled() {
    253         return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
    254     }
    255 
    256     public boolean isUnlockingDisabled() {
    257         return mDataCollector.isUnlockingDisabled();
    258     }
    259     /**
    260      * @return true if the classifier determined that this is not a human interacting with the phone
    261      */
    262     public boolean isFalseTouch() {
    263         if (FalsingLog.ENABLED) {
    264             // We're getting some false wtfs from touches that happen after the device went
    265             // to sleep. Only report missing sessions that happen when the device is interactive.
    266             if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive()
    267                     && mPendingWtf == null) {
    268                 int enabled = isEnabled() ? 1 : 0;
    269                 int screenOn = mScreenOn ? 1 : 0;
    270                 String state = StatusBarState.toShortString(mState);
    271                 Throwable here = new Throwable("here");
    272                 FalsingLog.wLogcat("isFalseTouch", new StringBuilder()
    273                         .append("Session is not active, yet there's a query for a false touch.")
    274                         .append(" enabled=").append(enabled)
    275                         .append(" mScreenOn=").append(screenOn)
    276                         .append(" mState=").append(state)
    277                         .append(". Escalating to WTF if screen does not turn on soon.")
    278                         .toString());
    279 
    280                 // Unfortunately we're also getting false positives for touches that happen right
    281                 // after the screen turns on, but before that notification has made it to us.
    282                 // Unfortunately there's no good way to catch that, except to wait and see if we get
    283                 // the screen on notification soon.
    284                 mPendingWtf = () -> FalsingLog.wtf("isFalseTouch", new StringBuilder()
    285                         .append("Session did not become active after query for a false touch.")
    286                         .append(" enabled=").append(enabled)
    287                         .append('/').append(isEnabled() ? 1 : 0)
    288                         .append(" mScreenOn=").append(screenOn)
    289                         .append('/').append(mScreenOn ? 1 : 0)
    290                         .append(" mState=").append(state)
    291                         .append('/').append(StatusBarState.toShortString(mState))
    292                         .append(". Look for warnings ~1000ms earlier to see root cause.")
    293                         .toString(), here);
    294                 mHandler.postDelayed(mPendingWtf, 1000);
    295             }
    296         }
    297         if (mAccessibilityManager.isTouchExplorationEnabled()) {
    298             // Touch exploration triggers false positives in the classifier and
    299             // already sufficiently prevents false unlocks.
    300             return false;
    301         }
    302         if (!mIsTouchScreen) {
    303             // Unlocking with input devices besides the touchscreen should already be sufficiently
    304             // anti-falsed.
    305             return false;
    306         }
    307         if (mJustUnlockedWithFace) {
    308             // Unlocking with face is a strong user presence signal, we can assume the user
    309             // is present until the next session starts.
    310             return false;
    311         }
    312         mIsFalseTouchCalls++;
    313         boolean isFalse = mHumanInteractionClassifier.isFalseTouch();
    314         if (!isFalse) {
    315             if (FalsingLog.ENABLED) {
    316                 FalsingLog.i("isFalseTouchCalls", "Calls before success: " + mIsFalseTouchCalls);
    317             }
    318             mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
    319             mIsFalseTouchCalls = 0;
    320         }
    321         return isFalse;
    322     }
    323 
    324     private void clearPendingWtf() {
    325         if (mPendingWtf != null) {
    326             mHandler.removeCallbacks(mPendingWtf);
    327             mPendingWtf = null;
    328         }
    329     }
    330 
    331 
    332     public boolean shouldEnforceBouncer() {
    333         return mEnforceBouncer;
    334     }
    335 
    336     public void setShowingAod(boolean showingAod) {
    337         mShowingAod = showingAod;
    338         updateSessionActive();
    339     }
    340 
    341     public void onScreenTurningOn() {
    342         if (FalsingLog.ENABLED) {
    343             FalsingLog.i("onScreenTurningOn", new StringBuilder()
    344                     .append("from=").append(mScreenOn ? 1 : 0)
    345                     .toString());
    346             clearPendingWtf();
    347         }
    348         mScreenOn = true;
    349         if (sessionEntrypoint()) {
    350             mDataCollector.onScreenTurningOn();
    351         }
    352     }
    353 
    354     public void onScreenOnFromTouch() {
    355         if (FalsingLog.ENABLED) {
    356             FalsingLog.i("onScreenOnFromTouch", new StringBuilder()
    357                     .append("from=").append(mScreenOn ? 1 : 0)
    358                     .toString());
    359         }
    360         mScreenOn = true;
    361         if (sessionEntrypoint()) {
    362             mDataCollector.onScreenOnFromTouch();
    363         }
    364     }
    365 
    366     public void onScreenOff() {
    367         if (FalsingLog.ENABLED) {
    368             FalsingLog.i("onScreenOff", new StringBuilder()
    369                     .append("from=").append(mScreenOn ? 1 : 0)
    370                     .toString());
    371         }
    372         mDataCollector.onScreenOff();
    373         mScreenOn = false;
    374         sessionExitpoint(false /* force */);
    375     }
    376 
    377     public void onSucccessfulUnlock() {
    378         if (FalsingLog.ENABLED) {
    379             FalsingLog.i("onSucccessfulUnlock", "");
    380         }
    381         mDataCollector.onSucccessfulUnlock();
    382     }
    383 
    384     public void onBouncerShown() {
    385         if (FalsingLog.ENABLED) {
    386             FalsingLog.i("onBouncerShown", new StringBuilder()
    387                     .append("from=").append(mBouncerOn ? 1 : 0)
    388                     .toString());
    389         }
    390         if (!mBouncerOn) {
    391             mBouncerOn = true;
    392             mDataCollector.onBouncerShown();
    393         }
    394     }
    395 
    396     public void onBouncerHidden() {
    397         if (FalsingLog.ENABLED) {
    398             FalsingLog.i("onBouncerHidden", new StringBuilder()
    399                     .append("from=").append(mBouncerOn ? 1 : 0)
    400                     .toString());
    401         }
    402         if (mBouncerOn) {
    403             mBouncerOn = false;
    404             mDataCollector.onBouncerHidden();
    405         }
    406     }
    407 
    408     public void onQsDown() {
    409         if (FalsingLog.ENABLED) {
    410             FalsingLog.i("onQsDown", "");
    411         }
    412         mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
    413         mDataCollector.onQsDown();
    414     }
    415 
    416     public void setQsExpanded(boolean expanded) {
    417         mDataCollector.setQsExpanded(expanded);
    418     }
    419 
    420     public void onTrackingStarted(boolean secure) {
    421         if (FalsingLog.ENABLED) {
    422             FalsingLog.i("onTrackingStarted", "");
    423         }
    424         mHumanInteractionClassifier.setType(secure
    425                 ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
    426         mDataCollector.onTrackingStarted();
    427     }
    428 
    429     public void onTrackingStopped() {
    430         mDataCollector.onTrackingStopped();
    431     }
    432 
    433     public void onNotificationActive() {
    434         mDataCollector.onNotificationActive();
    435     }
    436 
    437     public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
    438         if (FalsingLog.ENABLED) {
    439             FalsingLog.i("onNotificationDoubleTap", "accepted=" + accepted
    440                     + " dx=" + dx + " dy=" + dy + " (px)");
    441         }
    442         mDataCollector.onNotificationDoubleTap();
    443     }
    444 
    445     public void setNotificationExpanded() {
    446         mDataCollector.setNotificationExpanded();
    447     }
    448 
    449     public void onNotificatonStartDraggingDown() {
    450         if (FalsingLog.ENABLED) {
    451             FalsingLog.i("onNotificatonStartDraggingDown", "");
    452         }
    453         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN);
    454         mDataCollector.onNotificatonStartDraggingDown();
    455     }
    456 
    457     public void onStartExpandingFromPulse() {
    458         if (FalsingLog.ENABLED) {
    459             FalsingLog.i("onStartExpandingFromPulse", "");
    460         }
    461         mHumanInteractionClassifier.setType(Classifier.PULSE_EXPAND);
    462         mDataCollector.onStartExpandingFromPulse();
    463     }
    464 
    465     public void onNotificatonStopDraggingDown() {
    466         mDataCollector.onNotificatonStopDraggingDown();
    467     }
    468 
    469     public void onExpansionFromPulseStopped() {
    470         mDataCollector.onExpansionFromPulseStopped();
    471     }
    472 
    473     public void onNotificationDismissed() {
    474         mDataCollector.onNotificationDismissed();
    475     }
    476 
    477     public void onNotificatonStartDismissing() {
    478         if (FalsingLog.ENABLED) {
    479             FalsingLog.i("onNotificatonStartDismissing", "");
    480         }
    481         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
    482         mDataCollector.onNotificatonStartDismissing();
    483     }
    484 
    485     public void onNotificatonStopDismissing() {
    486         mDataCollector.onNotificatonStopDismissing();
    487     }
    488 
    489     public void onCameraOn() {
    490         mDataCollector.onCameraOn();
    491     }
    492 
    493     public void onLeftAffordanceOn() {
    494         mDataCollector.onLeftAffordanceOn();
    495     }
    496 
    497     public void onAffordanceSwipingStarted(boolean rightCorner) {
    498         if (FalsingLog.ENABLED) {
    499             FalsingLog.i("onAffordanceSwipingStarted", "");
    500         }
    501         if (rightCorner) {
    502             mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE);
    503         } else {
    504             mHumanInteractionClassifier.setType(Classifier.LEFT_AFFORDANCE);
    505         }
    506         mDataCollector.onAffordanceSwipingStarted(rightCorner);
    507     }
    508 
    509     public void onAffordanceSwipingAborted() {
    510         mDataCollector.onAffordanceSwipingAborted();
    511     }
    512 
    513     public void onUnlockHintStarted() {
    514         mDataCollector.onUnlockHintStarted();
    515     }
    516 
    517     public void onCameraHintStarted() {
    518         mDataCollector.onCameraHintStarted();
    519     }
    520 
    521     public void onLeftAffordanceHintStarted() {
    522         mDataCollector.onLeftAffordanceHintStarted();
    523     }
    524 
    525     public void onTouchEvent(MotionEvent event, int width, int height) {
    526         if (event.getAction() == MotionEvent.ACTION_DOWN) {
    527             mIsTouchScreen = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
    528             // If the bouncer was not shown during the down event,
    529             // we want the entire gesture going to HumanInteractionClassifier
    530             mBouncerOffOnDown = !mBouncerOn;
    531         }
    532         if (mSessionActive) {
    533             if (!mBouncerOn) {
    534                 // In case bouncer is "visible", but onFullyShown has not yet been called,
    535                 // avoid adding the event to DataCollector
    536                 mDataCollector.onTouchEvent(event, width, height);
    537             }
    538             if (mBouncerOffOnDown) {
    539                 mHumanInteractionClassifier.onTouchEvent(event);
    540             }
    541         }
    542     }
    543 
    544     public void dump(PrintWriter pw) {
    545         pw.println("FALSING MANAGER");
    546         pw.print("classifierEnabled="); pw.println(isClassiferEnabled() ? 1 : 0);
    547         pw.print("mSessionActive="); pw.println(mSessionActive ? 1 : 0);
    548         pw.print("mBouncerOn="); pw.println(mSessionActive ? 1 : 0);
    549         pw.print("mState="); pw.println(StatusBarState.toShortString(mState));
    550         pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
    551         pw.println();
    552     }
    553 
    554     @Override
    555     public void cleanup() {
    556         mSensorManager.unregisterListener(mSensorEventListener);
    557         mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
    558         Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
    559         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mKeyguardUpdateCallback);
    560     }
    561 
    562     public Uri reportRejectedTouch() {
    563         if (mDataCollector.isEnabled()) {
    564             return mDataCollector.reportRejectedTouch();
    565         }
    566         return null;
    567     }
    568 
    569     public boolean isReportingEnabled() {
    570         return mDataCollector.isReportingEnabled();
    571     }
    572 }
    573