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