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