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