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