1 /*
2  * Copyright (C) 2019 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.hardware.biometrics.BiometricSourceType;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.PowerManager;
30 import android.os.UserHandle;
31 import android.provider.Settings;
32 import android.view.InputDevice;
33 import android.view.MotionEvent;
34 import android.view.accessibility.AccessibilityManager;
35 
36 import com.android.internal.logging.MetricsLogger;
37 import com.android.keyguard.KeyguardUpdateMonitor;
38 import com.android.keyguard.KeyguardUpdateMonitorCallback;
39 import com.android.systemui.Dependency;
40 import com.android.systemui.UiOffloadThread;
41 import com.android.systemui.analytics.DataCollector;
42 import com.android.systemui.plugins.FalsingManager;
43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
44 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
45 import com.android.systemui.statusbar.StatusBarState;
46 import com.android.systemui.util.AsyncSensorManager;
47 
48 import java.io.PrintWriter;
49 
50 /**
51  * When the phone is locked, listens to touch, sensor and phone events and sends them to
52  * DataCollector and HumanInteractionClassifier.
53  *
54  * It does not collect touch events when the bouncer shows up.
55  */
56 public class FalsingManagerImpl implements FalsingManager {
57     private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
58 
59     private static final int[] CLASSIFIER_SENSORS = new int[] {
60             Sensor.TYPE_PROXIMITY,
61     };
62 
63     private static final int[] COLLECTOR_SENSORS = new int[] {
64             Sensor.TYPE_ACCELEROMETER,
65             Sensor.TYPE_GYROSCOPE,
66             Sensor.TYPE_PROXIMITY,
67             Sensor.TYPE_LIGHT,
68             Sensor.TYPE_ROTATION_VECTOR,
69     };
70     private static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
71     private static final String FALSING_SUCCESS = "falsing_success_after_attempts";
72 
73     private final Handler mHandler = new Handler(Looper.getMainLooper());
74     private final Context mContext;
75 
76     private final SensorManager mSensorManager;
77     private final DataCollector mDataCollector;
78     private final HumanInteractionClassifier mHumanInteractionClassifier;
79     private final AccessibilityManager mAccessibilityManager;
80     private final UiOffloadThread mUiOffloadThread;
81 
82     private boolean mEnforceBouncer = false;
83     private boolean mBouncerOn = false;
84     private boolean mBouncerOffOnDown = false;
85     private boolean mSessionActive = false;
86     private boolean mIsTouchScreen = true;
87     private boolean mJustUnlockedWithFace = false;
88     private int mState = StatusBarState.SHADE;
89     private boolean mScreenOn;
90     private boolean mShowingAod;
91     private Runnable mPendingWtf;
92     private int mIsFalseTouchCalls;
93     private MetricsLogger mMetricsLogger;
94 
95     private SensorEventListener mSensorEventListener = new SensorEventListener() {
96         @Override
97         public synchronized void onSensorChanged(SensorEvent event) {
98             mDataCollector.onSensorChanged(event);
99             mHumanInteractionClassifier.onSensorChanged(event);
100         }
101 
102         @Override
103         public void onAccuracyChanged(Sensor sensor, int accuracy) {
104             mDataCollector.onAccuracyChanged(sensor, accuracy);
105         }
106     };
107 
108     public StateListener mStatusBarStateListener = new StateListener() {
109         @Override
110         public void onStateChanged(int newState) {
111             if (FalsingLog.ENABLED) {
112                 FalsingLog.i("setStatusBarState", new StringBuilder()
113                         .append("from=").append(StatusBarState.toShortString(mState))
114                         .append(" to=").append(StatusBarState.toShortString(newState))
115                         .toString());
116             }
117             mState = newState;
118             updateSessionActive();
119         }
120     };
121 
122     protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
123         @Override
124         public void onChange(boolean selfChange) {
125             updateConfiguration();
126         }
127     };
128     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
129             new KeyguardUpdateMonitorCallback() {
130                 @Override
131                 public void onBiometricAuthenticated(int userId,
132                         BiometricSourceType biometricSourceType) {
133                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
134                             && biometricSourceType == BiometricSourceType.FACE) {
135                         mJustUnlockedWithFace = true;
136                     }
137                 }
138             };
139 
FalsingManagerImpl(Context context)140     FalsingManagerImpl(Context context) {
141         mContext = context;
142         mSensorManager = Dependency.get(AsyncSensorManager.class);
143         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
144         mDataCollector = DataCollector.getInstance(mContext);
145         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
146         mUiOffloadThread = Dependency.get(UiOffloadThread.class);
147         mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
148         mMetricsLogger = new MetricsLogger();
149 
150         mContext.getContentResolver().registerContentObserver(
151                 Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
152                 mSettingsObserver,
153                 UserHandle.USER_ALL);
154 
155         updateConfiguration();
156         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
157         KeyguardUpdateMonitor.getInstance(context).registerCallback(mKeyguardUpdateCallback);
158     }
159 
updateConfiguration()160     private void updateConfiguration() {
161         mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
162                 ENFORCE_BOUNCER, 0);
163     }
164 
shouldSessionBeActive()165     private boolean shouldSessionBeActive() {
166         if (FalsingLog.ENABLED && FalsingLog.VERBOSE) {
167             FalsingLog.v("shouldBeActive", new StringBuilder()
168                     .append("enabled=").append(isEnabled() ? 1 : 0)
169                     .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
170                     .append(" mState=").append(StatusBarState.toShortString(mState))
171                     .append(" mShowingAod=").append(mShowingAod ? 1 : 0)
172                     .toString()
173             );
174         }
175         return isEnabled() && mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
176     }
177 
sessionEntrypoint()178     private boolean sessionEntrypoint() {
179         if (!mSessionActive && shouldSessionBeActive()) {
180             onSessionStart();
181             return true;
182         }
183         return false;
184     }
185 
sessionExitpoint(boolean force)186     private void sessionExitpoint(boolean force) {
187         if (mSessionActive && (force || !shouldSessionBeActive())) {
188             mSessionActive = false;
189             if (mIsFalseTouchCalls != 0) {
190                 if (FalsingLog.ENABLED) {
191                     FalsingLog.i(
192                             "isFalseTouchCalls", "Calls before failure: " + mIsFalseTouchCalls);
193                 }
194                 mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
195                 mIsFalseTouchCalls = 0;
196             }
197 
198             // This can be expensive, and doesn't need to happen on the main thread.
199             mUiOffloadThread.submit(() -> {
200                 mSensorManager.unregisterListener(mSensorEventListener);
201             });
202         }
203     }
204 
updateSessionActive()205     public void updateSessionActive() {
206         if (shouldSessionBeActive()) {
207             sessionEntrypoint();
208         } else {
209             sessionExitpoint(false /* force */);
210         }
211     }
212 
onSessionStart()213     private void onSessionStart() {
214         if (FalsingLog.ENABLED) {
215             FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassiferEnabled());
216             clearPendingWtf();
217         }
218         mBouncerOn = false;
219         mSessionActive = true;
220         mJustUnlockedWithFace = false;
221         mIsFalseTouchCalls = 0;
222 
223         if (mHumanInteractionClassifier.isEnabled()) {
224             registerSensors(CLASSIFIER_SENSORS);
225         }
226         if (mDataCollector.isEnabledFull()) {
227             registerSensors(COLLECTOR_SENSORS);
228         }
229         if (mDataCollector.isEnabled()) {
230             mDataCollector.onFalsingSessionStarted();
231         }
232     }
233 
registerSensors(int [] sensors)234     private void registerSensors(int [] sensors) {
235         for (int sensorType : sensors) {
236             Sensor s = mSensorManager.getDefaultSensor(sensorType);
237             if (s != null) {
238 
239                 // This can be expensive, and doesn't need to happen on the main thread.
240                 mUiOffloadThread.submit(() -> {
241                     mSensorManager.registerListener(
242                             mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
243                 });
244             }
245         }
246     }
247 
isClassiferEnabled()248     public boolean isClassiferEnabled() {
249         return mHumanInteractionClassifier.isEnabled();
250     }
251 
isEnabled()252     private boolean isEnabled() {
253         return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
254     }
255 
isUnlockingDisabled()256     public boolean isUnlockingDisabled() {
257         return mDataCollector.isUnlockingDisabled();
258     }
259     /**
260      * @return true if the classifier determined that this is not a human interacting with the phone
261      */
isFalseTouch()262     public boolean isFalseTouch() {
263         if (FalsingLog.ENABLED) {
264             // We're getting some false wtfs from touches that happen after the device went
265             // to sleep. Only report missing sessions that happen when the device is interactive.
266             if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive()
267                     && mPendingWtf == null) {
268                 int enabled = isEnabled() ? 1 : 0;
269                 int screenOn = mScreenOn ? 1 : 0;
270                 String state = StatusBarState.toShortString(mState);
271                 Throwable here = new Throwable("here");
272                 FalsingLog.wLogcat("isFalseTouch", new StringBuilder()
273                         .append("Session is not active, yet there's a query for a false touch.")
274                         .append(" enabled=").append(enabled)
275                         .append(" mScreenOn=").append(screenOn)
276                         .append(" mState=").append(state)
277                         .append(". Escalating to WTF if screen does not turn on soon.")
278                         .toString());
279 
280                 // Unfortunately we're also getting false positives for touches that happen right
281                 // after the screen turns on, but before that notification has made it to us.
282                 // Unfortunately there's no good way to catch that, except to wait and see if we get
283                 // the screen on notification soon.
284                 mPendingWtf = () -> FalsingLog.wtf("isFalseTouch", new StringBuilder()
285                         .append("Session did not become active after query for a false touch.")
286                         .append(" enabled=").append(enabled)
287                         .append('/').append(isEnabled() ? 1 : 0)
288                         .append(" mScreenOn=").append(screenOn)
289                         .append('/').append(mScreenOn ? 1 : 0)
290                         .append(" mState=").append(state)
291                         .append('/').append(StatusBarState.toShortString(mState))
292                         .append(". Look for warnings ~1000ms earlier to see root cause.")
293                         .toString(), here);
294                 mHandler.postDelayed(mPendingWtf, 1000);
295             }
296         }
297         if (mAccessibilityManager.isTouchExplorationEnabled()) {
298             // Touch exploration triggers false positives in the classifier and
299             // already sufficiently prevents false unlocks.
300             return false;
301         }
302         if (!mIsTouchScreen) {
303             // Unlocking with input devices besides the touchscreen should already be sufficiently
304             // anti-falsed.
305             return false;
306         }
307         if (mJustUnlockedWithFace) {
308             // Unlocking with face is a strong user presence signal, we can assume the user
309             // is present until the next session starts.
310             return false;
311         }
312         mIsFalseTouchCalls++;
313         boolean isFalse = mHumanInteractionClassifier.isFalseTouch();
314         if (!isFalse) {
315             if (FalsingLog.ENABLED) {
316                 FalsingLog.i("isFalseTouchCalls", "Calls before success: " + mIsFalseTouchCalls);
317             }
318             mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
319             mIsFalseTouchCalls = 0;
320         }
321         return isFalse;
322     }
323 
clearPendingWtf()324     private void clearPendingWtf() {
325         if (mPendingWtf != null) {
326             mHandler.removeCallbacks(mPendingWtf);
327             mPendingWtf = null;
328         }
329     }
330 
331 
shouldEnforceBouncer()332     public boolean shouldEnforceBouncer() {
333         return mEnforceBouncer;
334     }
335 
setShowingAod(boolean showingAod)336     public void setShowingAod(boolean showingAod) {
337         mShowingAod = showingAod;
338         updateSessionActive();
339     }
340 
onScreenTurningOn()341     public void onScreenTurningOn() {
342         if (FalsingLog.ENABLED) {
343             FalsingLog.i("onScreenTurningOn", new StringBuilder()
344                     .append("from=").append(mScreenOn ? 1 : 0)
345                     .toString());
346             clearPendingWtf();
347         }
348         mScreenOn = true;
349         if (sessionEntrypoint()) {
350             mDataCollector.onScreenTurningOn();
351         }
352     }
353 
onScreenOnFromTouch()354     public void onScreenOnFromTouch() {
355         if (FalsingLog.ENABLED) {
356             FalsingLog.i("onScreenOnFromTouch", new StringBuilder()
357                     .append("from=").append(mScreenOn ? 1 : 0)
358                     .toString());
359         }
360         mScreenOn = true;
361         if (sessionEntrypoint()) {
362             mDataCollector.onScreenOnFromTouch();
363         }
364     }
365 
onScreenOff()366     public void onScreenOff() {
367         if (FalsingLog.ENABLED) {
368             FalsingLog.i("onScreenOff", new StringBuilder()
369                     .append("from=").append(mScreenOn ? 1 : 0)
370                     .toString());
371         }
372         mDataCollector.onScreenOff();
373         mScreenOn = false;
374         sessionExitpoint(false /* force */);
375     }
376 
onSucccessfulUnlock()377     public void onSucccessfulUnlock() {
378         if (FalsingLog.ENABLED) {
379             FalsingLog.i("onSucccessfulUnlock", "");
380         }
381         mDataCollector.onSucccessfulUnlock();
382     }
383 
onBouncerShown()384     public void onBouncerShown() {
385         if (FalsingLog.ENABLED) {
386             FalsingLog.i("onBouncerShown", new StringBuilder()
387                     .append("from=").append(mBouncerOn ? 1 : 0)
388                     .toString());
389         }
390         if (!mBouncerOn) {
391             mBouncerOn = true;
392             mDataCollector.onBouncerShown();
393         }
394     }
395 
onBouncerHidden()396     public void onBouncerHidden() {
397         if (FalsingLog.ENABLED) {
398             FalsingLog.i("onBouncerHidden", new StringBuilder()
399                     .append("from=").append(mBouncerOn ? 1 : 0)
400                     .toString());
401         }
402         if (mBouncerOn) {
403             mBouncerOn = false;
404             mDataCollector.onBouncerHidden();
405         }
406     }
407 
onQsDown()408     public void onQsDown() {
409         if (FalsingLog.ENABLED) {
410             FalsingLog.i("onQsDown", "");
411         }
412         mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
413         mDataCollector.onQsDown();
414     }
415 
setQsExpanded(boolean expanded)416     public void setQsExpanded(boolean expanded) {
417         mDataCollector.setQsExpanded(expanded);
418     }
419 
onTrackingStarted(boolean secure)420     public void onTrackingStarted(boolean secure) {
421         if (FalsingLog.ENABLED) {
422             FalsingLog.i("onTrackingStarted", "");
423         }
424         mHumanInteractionClassifier.setType(secure
425                 ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
426         mDataCollector.onTrackingStarted();
427     }
428 
onTrackingStopped()429     public void onTrackingStopped() {
430         mDataCollector.onTrackingStopped();
431     }
432 
onNotificationActive()433     public void onNotificationActive() {
434         mDataCollector.onNotificationActive();
435     }
436 
onNotificationDoubleTap(boolean accepted, float dx, float dy)437     public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
438         if (FalsingLog.ENABLED) {
439             FalsingLog.i("onNotificationDoubleTap", "accepted=" + accepted
440                     + " dx=" + dx + " dy=" + dy + " (px)");
441         }
442         mDataCollector.onNotificationDoubleTap();
443     }
444 
setNotificationExpanded()445     public void setNotificationExpanded() {
446         mDataCollector.setNotificationExpanded();
447     }
448 
onNotificatonStartDraggingDown()449     public void onNotificatonStartDraggingDown() {
450         if (FalsingLog.ENABLED) {
451             FalsingLog.i("onNotificatonStartDraggingDown", "");
452         }
453         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN);
454         mDataCollector.onNotificatonStartDraggingDown();
455     }
456 
onStartExpandingFromPulse()457     public void onStartExpandingFromPulse() {
458         if (FalsingLog.ENABLED) {
459             FalsingLog.i("onStartExpandingFromPulse", "");
460         }
461         mHumanInteractionClassifier.setType(Classifier.PULSE_EXPAND);
462         mDataCollector.onStartExpandingFromPulse();
463     }
464 
onNotificatonStopDraggingDown()465     public void onNotificatonStopDraggingDown() {
466         mDataCollector.onNotificatonStopDraggingDown();
467     }
468 
onExpansionFromPulseStopped()469     public void onExpansionFromPulseStopped() {
470         mDataCollector.onExpansionFromPulseStopped();
471     }
472 
onNotificationDismissed()473     public void onNotificationDismissed() {
474         mDataCollector.onNotificationDismissed();
475     }
476 
onNotificatonStartDismissing()477     public void onNotificatonStartDismissing() {
478         if (FalsingLog.ENABLED) {
479             FalsingLog.i("onNotificatonStartDismissing", "");
480         }
481         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
482         mDataCollector.onNotificatonStartDismissing();
483     }
484 
onNotificatonStopDismissing()485     public void onNotificatonStopDismissing() {
486         mDataCollector.onNotificatonStopDismissing();
487     }
488 
onCameraOn()489     public void onCameraOn() {
490         mDataCollector.onCameraOn();
491     }
492 
onLeftAffordanceOn()493     public void onLeftAffordanceOn() {
494         mDataCollector.onLeftAffordanceOn();
495     }
496 
onAffordanceSwipingStarted(boolean rightCorner)497     public void onAffordanceSwipingStarted(boolean rightCorner) {
498         if (FalsingLog.ENABLED) {
499             FalsingLog.i("onAffordanceSwipingStarted", "");
500         }
501         if (rightCorner) {
502             mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE);
503         } else {
504             mHumanInteractionClassifier.setType(Classifier.LEFT_AFFORDANCE);
505         }
506         mDataCollector.onAffordanceSwipingStarted(rightCorner);
507     }
508 
onAffordanceSwipingAborted()509     public void onAffordanceSwipingAborted() {
510         mDataCollector.onAffordanceSwipingAborted();
511     }
512 
onUnlockHintStarted()513     public void onUnlockHintStarted() {
514         mDataCollector.onUnlockHintStarted();
515     }
516 
onCameraHintStarted()517     public void onCameraHintStarted() {
518         mDataCollector.onCameraHintStarted();
519     }
520 
onLeftAffordanceHintStarted()521     public void onLeftAffordanceHintStarted() {
522         mDataCollector.onLeftAffordanceHintStarted();
523     }
524 
onTouchEvent(MotionEvent event, int width, int height)525     public void onTouchEvent(MotionEvent event, int width, int height) {
526         if (event.getAction() == MotionEvent.ACTION_DOWN) {
527             mIsTouchScreen = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
528             // If the bouncer was not shown during the down event,
529             // we want the entire gesture going to HumanInteractionClassifier
530             mBouncerOffOnDown = !mBouncerOn;
531         }
532         if (mSessionActive) {
533             if (!mBouncerOn) {
534                 // In case bouncer is "visible", but onFullyShown has not yet been called,
535                 // avoid adding the event to DataCollector
536                 mDataCollector.onTouchEvent(event, width, height);
537             }
538             if (mBouncerOffOnDown) {
539                 mHumanInteractionClassifier.onTouchEvent(event);
540             }
541         }
542     }
543 
dump(PrintWriter pw)544     public void dump(PrintWriter pw) {
545         pw.println("FALSING MANAGER");
546         pw.print("classifierEnabled="); pw.println(isClassiferEnabled() ? 1 : 0);
547         pw.print("mSessionActive="); pw.println(mSessionActive ? 1 : 0);
548         pw.print("mBouncerOn="); pw.println(mSessionActive ? 1 : 0);
549         pw.print("mState="); pw.println(StatusBarState.toShortString(mState));
550         pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
551         pw.println();
552     }
553 
554     @Override
cleanup()555     public void cleanup() {
556         mSensorManager.unregisterListener(mSensorEventListener);
557         mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
558         Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
559         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mKeyguardUpdateCallback);
560     }
561 
reportRejectedTouch()562     public Uri reportRejectedTouch() {
563         if (mDataCollector.isEnabled()) {
564             return mDataCollector.reportRejectedTouch();
565         }
566         return null;
567     }
568 
isReportingEnabled()569     public boolean isReportingEnabled() {
570         return mDataCollector.isReportingEnabled();
571     }
572 }
573