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