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