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