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