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 FalsingManagerImpl(Context context)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 updateConfiguration()160 private void updateConfiguration() { 161 mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(), 162 ENFORCE_BOUNCER, 0); 163 } 164 shouldSessionBeActive()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 sessionEntrypoint()178 private boolean sessionEntrypoint() { 179 if (!mSessionActive && shouldSessionBeActive()) { 180 onSessionStart(); 181 return true; 182 } 183 return false; 184 } 185 sessionExitpoint(boolean force)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 updateSessionActive()205 public void updateSessionActive() { 206 if (shouldSessionBeActive()) { 207 sessionEntrypoint(); 208 } else { 209 sessionExitpoint(false /* force */); 210 } 211 } 212 onSessionStart()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 registerSensors(int [] sensors)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 isClassiferEnabled()248 public boolean isClassiferEnabled() { 249 return mHumanInteractionClassifier.isEnabled(); 250 } 251 isEnabled()252 private boolean isEnabled() { 253 return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled(); 254 } 255 isUnlockingDisabled()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 */ isFalseTouch()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 clearPendingWtf()324 private void clearPendingWtf() { 325 if (mPendingWtf != null) { 326 mHandler.removeCallbacks(mPendingWtf); 327 mPendingWtf = null; 328 } 329 } 330 331 shouldEnforceBouncer()332 public boolean shouldEnforceBouncer() { 333 return mEnforceBouncer; 334 } 335 setShowingAod(boolean showingAod)336 public void setShowingAod(boolean showingAod) { 337 mShowingAod = showingAod; 338 updateSessionActive(); 339 } 340 onScreenTurningOn()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 onScreenOnFromTouch()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 onScreenOff()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 onSucccessfulUnlock()377 public void onSucccessfulUnlock() { 378 if (FalsingLog.ENABLED) { 379 FalsingLog.i("onSucccessfulUnlock", ""); 380 } 381 mDataCollector.onSucccessfulUnlock(); 382 } 383 onBouncerShown()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 onBouncerHidden()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 onQsDown()408 public void onQsDown() { 409 if (FalsingLog.ENABLED) { 410 FalsingLog.i("onQsDown", ""); 411 } 412 mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS); 413 mDataCollector.onQsDown(); 414 } 415 setQsExpanded(boolean expanded)416 public void setQsExpanded(boolean expanded) { 417 mDataCollector.setQsExpanded(expanded); 418 } 419 onTrackingStarted(boolean secure)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 onTrackingStopped()429 public void onTrackingStopped() { 430 mDataCollector.onTrackingStopped(); 431 } 432 onNotificationActive()433 public void onNotificationActive() { 434 mDataCollector.onNotificationActive(); 435 } 436 onNotificationDoubleTap(boolean accepted, float dx, float dy)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 setNotificationExpanded()445 public void setNotificationExpanded() { 446 mDataCollector.setNotificationExpanded(); 447 } 448 onNotificatonStartDraggingDown()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 onStartExpandingFromPulse()457 public void onStartExpandingFromPulse() { 458 if (FalsingLog.ENABLED) { 459 FalsingLog.i("onStartExpandingFromPulse", ""); 460 } 461 mHumanInteractionClassifier.setType(Classifier.PULSE_EXPAND); 462 mDataCollector.onStartExpandingFromPulse(); 463 } 464 onNotificatonStopDraggingDown()465 public void onNotificatonStopDraggingDown() { 466 mDataCollector.onNotificatonStopDraggingDown(); 467 } 468 onExpansionFromPulseStopped()469 public void onExpansionFromPulseStopped() { 470 mDataCollector.onExpansionFromPulseStopped(); 471 } 472 onNotificationDismissed()473 public void onNotificationDismissed() { 474 mDataCollector.onNotificationDismissed(); 475 } 476 onNotificatonStartDismissing()477 public void onNotificatonStartDismissing() { 478 if (FalsingLog.ENABLED) { 479 FalsingLog.i("onNotificatonStartDismissing", ""); 480 } 481 mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS); 482 mDataCollector.onNotificatonStartDismissing(); 483 } 484 onNotificatonStopDismissing()485 public void onNotificatonStopDismissing() { 486 mDataCollector.onNotificatonStopDismissing(); 487 } 488 onCameraOn()489 public void onCameraOn() { 490 mDataCollector.onCameraOn(); 491 } 492 onLeftAffordanceOn()493 public void onLeftAffordanceOn() { 494 mDataCollector.onLeftAffordanceOn(); 495 } 496 onAffordanceSwipingStarted(boolean rightCorner)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 onAffordanceSwipingAborted()509 public void onAffordanceSwipingAborted() { 510 mDataCollector.onAffordanceSwipingAborted(); 511 } 512 onUnlockHintStarted()513 public void onUnlockHintStarted() { 514 mDataCollector.onUnlockHintStarted(); 515 } 516 onCameraHintStarted()517 public void onCameraHintStarted() { 518 mDataCollector.onCameraHintStarted(); 519 } 520 onLeftAffordanceHintStarted()521 public void onLeftAffordanceHintStarted() { 522 mDataCollector.onLeftAffordanceHintStarted(); 523 } 524 onTouchEvent(MotionEvent event, int width, int height)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 dump(PrintWriter pw)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 cleanup()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 reportRejectedTouch()562 public Uri reportRejectedTouch() { 563 if (mDataCollector.isEnabled()) { 564 return mDataCollector.reportRejectedTouch(); 565 } 566 return null; 567 } 568 isReportingEnabled()569 public boolean isReportingEnabled() { 570 return mDataCollector.isReportingEnabled(); 571 } 572 } 573