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