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