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