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