• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     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 
FalsingManager(Context context)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 
getInstance(Context context)99     public static FalsingManager getInstance(Context context) {
100         if (sInstance == null) {
101             sInstance = new FalsingManager(context);
102         }
103         return sInstance;
104     }
105 
updateConfiguration()106     private void updateConfiguration() {
107         mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
108                 ENFORCE_BOUNCER, 0);
109     }
110 
shouldSessionBeActive()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 
sessionEntrypoint()122     private boolean sessionEntrypoint() {
123         if (!mSessionActive && shouldSessionBeActive()) {
124             onSessionStart();
125             return true;
126         }
127         return false;
128     }
129 
sessionExitpoint(boolean force)130     private void sessionExitpoint(boolean force) {
131         if (mSessionActive && (force || !shouldSessionBeActive())) {
132             mSessionActive = false;
133             mSensorManager.unregisterListener(this);
134         }
135     }
136 
onSessionStart()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 
registerSensors(int [] sensors)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 
isClassiferEnabled()162     public boolean isClassiferEnabled() {
163         return mHumanInteractionClassifier.isEnabled();
164     }
165 
isEnabled()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      */
isFalseTouch()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 
clearPendingWtf()216     private void clearPendingWtf() {
217         if (mPendingWtf != null) {
218             mHandler.removeCallbacks(mPendingWtf);
219             mPendingWtf = null;
220         }
221     }
222 
223     @Override
onSensorChanged(SensorEvent event)224     public synchronized void onSensorChanged(SensorEvent event) {
225         mDataCollector.onSensorChanged(event);
226         mHumanInteractionClassifier.onSensorChanged(event);
227     }
228 
229     @Override
onAccuracyChanged(Sensor sensor, int accuracy)230     public void onAccuracyChanged(Sensor sensor, int accuracy) {
231         mDataCollector.onAccuracyChanged(sensor, accuracy);
232     }
233 
shouldEnforceBouncer()234     public boolean shouldEnforceBouncer() {
235         return mEnforceBouncer;
236     }
237 
setStatusBarState(int state)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 
onScreenTurningOn()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 
onScreenOnFromTouch()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 
onScreenOff()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 
onSucccessfulUnlock()289     public void onSucccessfulUnlock() {
290         if (FalsingLog.ENABLED) {
291             FalsingLog.i("onSucccessfulUnlock", "");
292         }
293         mDataCollector.onSucccessfulUnlock();
294     }
295 
onBouncerShown()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 
onBouncerHidden()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 
onQsDown()320     public void onQsDown() {
321         if (FalsingLog.ENABLED) {
322             FalsingLog.i("onQsDown", "");
323         }
324         mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
325         mDataCollector.onQsDown();
326     }
327 
setQsExpanded(boolean expanded)328     public void setQsExpanded(boolean expanded) {
329         mDataCollector.setQsExpanded(expanded);
330     }
331 
onTrackingStarted()332     public void onTrackingStarted() {
333         if (FalsingLog.ENABLED) {
334             FalsingLog.i("onTrackingStarted", "");
335         }
336         mHumanInteractionClassifier.setType(Classifier.UNLOCK);
337         mDataCollector.onTrackingStarted();
338     }
339 
onTrackingStopped()340     public void onTrackingStopped() {
341         mDataCollector.onTrackingStopped();
342     }
343 
onNotificationActive()344     public void onNotificationActive() {
345         mDataCollector.onNotificationActive();
346     }
347 
onNotificationDoubleTap(boolean accepted, float dx, float dy)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 
setNotificationExpanded()356     public void setNotificationExpanded() {
357         mDataCollector.setNotificationExpanded();
358     }
359 
onNotificatonStartDraggingDown()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 
onNotificatonStopDraggingDown()368     public void onNotificatonStopDraggingDown() {
369         mDataCollector.onNotificatonStopDraggingDown();
370     }
371 
onNotificationDismissed()372     public void onNotificationDismissed() {
373         mDataCollector.onNotificationDismissed();
374     }
375 
onNotificatonStartDismissing()376     public void onNotificatonStartDismissing() {
377         if (FalsingLog.ENABLED) {
378             FalsingLog.i("onNotificatonStartDismissing", "");
379         }
380         mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
381         mDataCollector.onNotificatonStartDismissing();
382     }
383 
onNotificatonStopDismissing()384     public void onNotificatonStopDismissing() {
385         mDataCollector.onNotificatonStopDismissing();
386     }
387 
onCameraOn()388     public void onCameraOn() {
389         mDataCollector.onCameraOn();
390     }
391 
onLeftAffordanceOn()392     public void onLeftAffordanceOn() {
393         mDataCollector.onLeftAffordanceOn();
394     }
395 
onAffordanceSwipingStarted(boolean rightCorner)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 
onAffordanceSwipingAborted()408     public void onAffordanceSwipingAborted() {
409         mDataCollector.onAffordanceSwipingAborted();
410     }
411 
onUnlockHintStarted()412     public void onUnlockHintStarted() {
413         mDataCollector.onUnlockHintStarted();
414     }
415 
onCameraHintStarted()416     public void onCameraHintStarted() {
417         mDataCollector.onCameraHintStarted();
418     }
419 
onLeftAffordanceHintStarted()420     public void onLeftAffordanceHintStarted() {
421         mDataCollector.onLeftAffordanceHintStarted();
422     }
423 
onTouchEvent(MotionEvent event, int width, int height)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 
dump(PrintWriter pw)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 
reportRejectedTouch()441     public Uri reportRejectedTouch() {
442         if (mDataCollector.isEnabled()) {
443             return mDataCollector.reportRejectedTouch();
444         }
445         return null;
446     }
447 
isReportingEnabled()448     public boolean isReportingEnabled() {
449         return mDataCollector.isReportingEnabled();
450     }
451 }
452