Add FalsingManager and Classifier to SystemUI

Adds the possibility to analyze and classify touch and sensor events as
human or false touches.

Change-Id: I5079c02406d532fea38ca2d302e8606effae0696
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java
rename to packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
index e458862..91f6520 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
@@ -21,7 +21,6 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Handler;
@@ -30,8 +29,6 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
-import com.android.systemui.statusbar.StatusBarState;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -47,24 +44,29 @@
  * A session starts when the screen is turned on.
  * A session ends when the screen is turned off or user unlocks the phone.
  */
-public class LockedPhoneAnalytics implements SensorEventListener {
-    private static final String TAG = "LockedPhoneAnalytics";
-    private static final String ANALYTICS_ENABLE = "locked_phone_analytics_enable";
-    private static final String ENFORCE_BOUNCER = "locked_phone_analytics_enforce_bouncer";
-    private static final String COLLECT_BAD_TOCUHES = "locked_phone_analytics_collect_bad_touches";
+public class DataCollector implements SensorEventListener {
+    private static final String TAG = "DataCollector";
+    private static final String COLLECTOR_ENABLE = "data_collector_enable";
+    private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches";
 
     private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
     public static final boolean DEBUG = false;
 
-    private static final int[] SENSORS = new int[] {
-            Sensor.TYPE_ACCELEROMETER,
-            Sensor.TYPE_GYROSCOPE,
-            Sensor.TYPE_PROXIMITY,
-            Sensor.TYPE_LIGHT,
-            Sensor.TYPE_ROTATION_VECTOR,
-    };
-
     private final Handler mHandler = new Handler();
+    private final Context mContext;
+
+    // Err on the side of caution, so logging is not started after a crash even tough the screen
+    // is off.
+    private SensorLoggerSession mCurrentSession = null;
+
+    private boolean mEnableCollector = false;
+    private boolean mTimeoutActive = false;
+    private boolean mCollectBadTouches = false;
+    private boolean mCornerSwiping = false;
+    private boolean mTrackingStarted = false;
+
+    private static DataCollector sInstance = null;
+
     protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
@@ -72,69 +74,40 @@
         }
     };
 
-    private final SensorManager mSensorManager;
-    private final Context mContext;
-
-    // Err on the side of caution, so logging is not started after a crash even tough the screen
-    // is off.
-    private SensorLoggerSession mCurrentSession = null;
-
-    private boolean mEnableAnalytics = false;
-    private boolean mEnforceBouncer = false;
-    private boolean mTimeoutActive = false;
-    private boolean mCollectBadTouches = false;
-    private boolean mBouncerOn = false;
-    private boolean mCornerSwiping = false;
-    private boolean mTrackingStarted = false;
-
-    private int mState = StatusBarState.SHADE;
-
-    private static LockedPhoneAnalytics sInstance = null;
-
-    private LockedPhoneAnalytics(Context context) {
-        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+    private DataCollector(Context context) {
         mContext = context;
 
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(ANALYTICS_ENABLE), false,
+                Settings.Secure.getUriFor(COLLECTOR_ENABLE), false,
                 mSettingsObserver,
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
-                mSettingsObserver,
-                UserHandle.USER_ALL);
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(COLLECT_BAD_TOCUHES), false,
+                Settings.Secure.getUriFor(COLLECT_BAD_TOUCHES), false,
                 mSettingsObserver,
                 UserHandle.USER_ALL);
 
         updateConfiguration();
     }
 
-    public static LockedPhoneAnalytics getInstance(Context context) {
+    public static DataCollector getInstance(Context context) {
         if (sInstance == null) {
-            sInstance = new LockedPhoneAnalytics(context);
+            sInstance = new DataCollector(context);
         }
         return sInstance;
     }
 
     private void updateConfiguration() {
-        mEnableAnalytics = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
+        mEnableCollector = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
                 mContext.getContentResolver(),
-                ANALYTICS_ENABLE, 0);
-        mEnforceBouncer = mEnableAnalytics && 0 != Settings.Secure.getInt(
+                COLLECTOR_ENABLE, 0);
+        mCollectBadTouches = mEnableCollector && 0 != Settings.Secure.getInt(
                 mContext.getContentResolver(),
-                ENFORCE_BOUNCER, 0);
-        mCollectBadTouches = mEnableAnalytics && 0 != Settings.Secure.getInt(
-                mContext.getContentResolver(),
-                COLLECT_BAD_TOCUHES, 0);
+                COLLECT_BAD_TOUCHES, 0);
     }
 
     private boolean sessionEntrypoint() {
-        if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)
-                && mEnableAnalytics && mCurrentSession == null) {
+        if (mEnableCollector && mCurrentSession == null) {
             onSessionStart();
             return true;
         }
@@ -142,22 +115,15 @@
     }
 
     private void sessionExitpoint(int result) {
-        if (mEnableAnalytics && mCurrentSession != null) {
+        if (mEnableCollector && mCurrentSession != null) {
             onSessionEnd(result);
         }
     }
 
     private void onSessionStart() {
-        mBouncerOn = false;
         mCornerSwiping = false;
         mTrackingStarted = false;
         mCurrentSession = new SensorLoggerSession(System.currentTimeMillis(), System.nanoTime());
-        for (int sensorType : SENSORS) {
-            Sensor s = mSensorManager.getDefaultSensor(sensorType);
-            if (s != null) {
-                mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
-            }
-        }
     }
 
     private void onSessionEnd(int result) {
@@ -196,10 +162,9 @@
         });
     }
 
-
     @Override
     public synchronized void onSensorChanged(SensorEvent event) {
-        if (mEnableAnalytics && mCurrentSession != null) {
+        if (mEnableCollector && mCurrentSession != null) {
             mCurrentSession.addSensorEvent(event, System.nanoTime());
             enforceTimeout();
         }
@@ -221,18 +186,14 @@
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }
 
-    public boolean shouldEnforceBouncer() {
-        return mEnforceBouncer;
+    public boolean isEnabled() {
+        return mEnableCollector;
     }
 
-    public void setStatusBarState(int state) {
-        mState = state;
-    }
-
-    public void onScreenOn() {
+    public void onScreenTurningOn() {
         if (sessionEntrypoint()) {
             if (DEBUG) {
-                Log.d(TAG, "onScreenOn");
+                Log.d(TAG, "onScreenTurningOn");
             }
             addEvent(PhoneEvent.ON_SCREEN_ON);
         }
@@ -264,23 +225,17 @@
     }
 
     public void onBouncerShown() {
-        if (!mBouncerOn) {
-            if (DEBUG) {
-                Log.d(TAG, "onBouncerShown");
-            }
-            mBouncerOn = true;
-            addEvent(PhoneEvent.ON_BOUNCER_SHOWN);
+        if (DEBUG) {
+            Log.d(TAG, "onBouncerShown");
         }
+        addEvent(PhoneEvent.ON_BOUNCER_SHOWN);
     }
 
     public void onBouncerHidden() {
-        if (mBouncerOn) {
-            if (DEBUG) {
-                Log.d(TAG, "onBouncerHidden");
-            }
-            mBouncerOn = false;
-            addEvent(PhoneEvent.ON_BOUNCER_HIDDEN);
+        if (DEBUG) {
+            Log.d(TAG, "onBouncerHidden");
         }
+        addEvent(PhoneEvent.ON_BOUNCER_HIDDEN);
     }
 
     public void onQsDown() {
@@ -433,20 +388,20 @@
         addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_HINT_STARTED);
     }
 
-    public void onTouchEvent(MotionEvent ev, int width, int height) {
-        if (!mBouncerOn && mCurrentSession != null) {
+    public void onTouchEvent(MotionEvent event, int width, int height) {
+        if (mCurrentSession != null) {
             if (DEBUG) {
                 Log.v(TAG, "onTouchEvent(ev.action="
-                        + MotionEvent.actionToString(ev.getAction()) + ")");
+                        + MotionEvent.actionToString(event.getAction()) + ")");
             }
-            mCurrentSession.addMotionEvent(ev);
+            mCurrentSession.addMotionEvent(event);
             mCurrentSession.setTouchArea(width, height);
             enforceTimeout();
         }
     }
 
     private void addEvent(int eventType) {
-        if (mEnableAnalytics && mCurrentSession != null) {
+        if (mEnableCollector && mCurrentSession != null) {
             mCurrentSession.addPhoneEvent(eventType, System.nanoTime());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
index 09383f6a..0e28002 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
@@ -57,7 +57,7 @@
         mResult = result;
         mEndTimestampMillis = endTimestampMillis;
 
-        if (LockedPhoneAnalytics.DEBUG) {
+        if (DataCollector.DEBUG) {
             Log.d(TAG, "Ending session result=" + result + " it lasted for " +
                     (float) (mEndTimestampMillis - mStartTimestampMillis) / 1000f + "s");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java
new file mode 100644
index 0000000..5cd914f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import android.hardware.SensorEvent;
+import android.view.MotionEvent;
+
+import java.lang.Math;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A classifier which calculates the variance of differences between successive angles in a stroke.
+ * For each stroke it keeps its last three points. If some successive points are the same, it ignores
+ * the repetitions. If a new point is added, the classifier calculates the angle between the last
+ * three points. After that it calculates the difference between this angle and the previously
+ * calculated angle. The return value of the classifier is the variance of the differences
+ * from a stroke. If there are multiple strokes created at once, the classifier sums up the
+ * variances of all the strokes. Also the value is multiplied by HISTORY_FACTOR after each
+ * INTERVAL milliseconds.
+ */
+public class AnglesVarianceClassifier extends Classifier {
+    private final float INTERVAL = 10.0f;
+    private final float CLEAR_HISTORY = 500f;
+    private final float HISTORY_FACTOR = 0.9f;
+
+    private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
+    private float mValue;
+    private long mLastUpdate;
+
+    public AnglesVarianceClassifier(ClassifierData classifierData) {
+        mClassifierData = classifierData;
+        mValue = 0.0f;
+        mLastUpdate = System.currentTimeMillis();
+    }
+
+    @Override
+    public void onTouchEvent(MotionEvent event) {
+        int action = event.getActionMasked();
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            mStrokeMap.clear();
+        }
+
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
+
+            if (mStrokeMap.get(stroke) == null) {
+                mStrokeMap.put(stroke, new Data());
+            }
+            mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1));
+
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
+                    || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
+                decayValue();
+                mValue += mStrokeMap.get(stroke).getAnglesVariance();
+            }
+        }
+    }
+
+    /**
+     * Decreases mValue through time
+     */
+    private void decayValue() {
+        long currentTimeMillis = System.currentTimeMillis();
+        if (currentTimeMillis - mLastUpdate > CLEAR_HISTORY) {
+            mValue = 0.0f;
+        } else {
+            mValue *= Math.pow(HISTORY_FACTOR, (float) (currentTimeMillis - mLastUpdate) / INTERVAL);
+        }
+        mLastUpdate = currentTimeMillis;
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+    }
+
+    @Override
+    public float getFalseTouchEvaluation(int type) {
+        decayValue();
+        float currentValue = 0.0f;
+        for (Data data: mStrokeMap.values()) {
+            currentValue += data.getAnglesVariance();
+        }
+        return (float) (mValue + currentValue);
+    }
+
+    private class Data {
+        private List<Point> mLastThreePoints = new ArrayList<>();
+        private float mPreviousAngle;
+        private float mSumSquares;
+        private float mSum;
+        private float mCount;
+
+        public Data() {
+            mPreviousAngle = (float) Math.PI;
+            mSumSquares = 0.0f;
+            mSum = 0.0f;
+            mCount = 1.0f;
+        }
+
+        public void addPoint(Point point) {
+            // Checking if the added point is different than the previously added point
+            // Repetitions are being ignored so that proper angles are calculated.
+            if (mLastThreePoints.isEmpty()
+                    || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) {
+                mLastThreePoints.add(point);
+                if (mLastThreePoints.size() == 4) {
+                    mLastThreePoints.remove(0);
+
+                    float angle = getAngle(mLastThreePoints.get(0), mLastThreePoints.get(1),
+                            mLastThreePoints.get(2));
+
+                    float difference = angle - mPreviousAngle;
+                    mSum += difference;
+                    mSumSquares += difference * difference;
+                    mCount += 1.0;
+                    mPreviousAngle = angle;
+                }
+            }
+        }
+
+        private float getAngle(Point a, Point b, Point c) {
+            float dist1 = a.dist(b);
+            float dist2 = b.dist(c);
+            float crossProduct = b.crossProduct(a, c);
+            float dotProduct = b.dotProduct(a, c);
+            float cos = Math.min(1.0f, Math.max(-1.0f, dotProduct / dist1 / dist2));
+            float angle = (float) Math.acos(cos);
+            if (crossProduct < 0.0) {
+                angle = 2.0f * (float) Math.PI - angle;
+            }
+            return angle;
+        }
+
+        public float getAnglesVariance() {
+            return mSumSquares / mCount + (mSum / mCount) * (mSum / mCount);
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
new file mode 100644
index 0000000..b76be14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import android.hardware.SensorEvent;
+import android.view.MotionEvent;
+
+/**
+ * An interface for classifiers for touch and sensor events.
+ */
+public abstract class Classifier {
+    public static final int QUICK_SETTINGS = 0;
+    public static final int NOTIFICATION_DISMISS = 1;
+    public static final int NOTIFICATION_DRAG_DOWN = 2;
+    public static final int NOTIFICATION_DOUBLE_TAP = 3;
+    public static final int UNLOCK = 4;
+    public static final int LEFT_AFFORDANCE = 5;
+    public static final int RIGHT_AFFORDANCE = 6;
+
+    /**
+     * Contains all the information about touch events from which the classifier can query
+     */
+    protected ClassifierData mClassifierData;
+
+    /**
+     * Informs the classifier that a new touch event has occurred
+     */
+    public void onTouchEvent(MotionEvent event) {
+    }
+
+    /**
+     * Informs the classifier that a sensor change occurred
+     */
+    public void onSensorChanged(SensorEvent event) {
+    }
+
+    /**
+     * @param type the type of action for which this method is called
+     * @return a nonnegative value which is used to determine whether this a false touch. The
+     *         bigger the value the greater the chance that this a false touch.
+     */
+    public abstract float getFalseTouchEvaluation(int type);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
new file mode 100644
index 0000000..77b81d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import android.util.SparseArray;
+import android.view.MotionEvent;
+
+/**
+ * Contains data which is used to classify interaction sequences on the lockscreen. It does, for
+ * example, provide information on the current touch state.
+ */
+public class ClassifierData {
+    private SparseArray<Stroke> mCurrentStrokes = new SparseArray<>();
+
+    public ClassifierData() {
+    }
+
+    public void update(MotionEvent event) {
+        int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mCurrentStrokes.clear();
+        }
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            int id = event.getPointerId(i);
+            if (mCurrentStrokes.get(id) == null) {
+                mCurrentStrokes.put(id, new Stroke(event.getEventTimeNano()));
+            }
+            mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i),
+                    event.getEventTimeNano());
+        }
+    }
+
+    public void cleanUp(MotionEvent event) {
+        int action = event.getActionMasked();
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            int id = event.getPointerId(i);
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
+                    || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
+                mCurrentStrokes.remove(id);
+            }
+        }
+    }
+
+    /**
+     * @param id the id from MotionEvent
+     * @return the Stroke assigned to the id
+     */
+    public Stroke getStroke(int id) {
+        return mCurrentStrokes.get(id);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
new file mode 100644
index 0000000..347273a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.MotionEvent;
+
+import com.android.systemui.analytics.DataCollector;
+import com.android.systemui.statusbar.StatusBarState;
+
+/**
+ * When the phone is locked, listens to touch, sensor and phone events and sends them to
+ * DataCollector and HumanInteractionClassifier.
+ *
+ * It does not collect touch events when the bouncer shows up.
+ */
+public class FalsingManager implements SensorEventListener {
+    private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
+
+    private static final int[] SENSORS = new int[] {
+            Sensor.TYPE_ACCELEROMETER,
+            Sensor.TYPE_GYROSCOPE,
+            Sensor.TYPE_PROXIMITY,
+            Sensor.TYPE_LIGHT,
+            Sensor.TYPE_ROTATION_VECTOR,
+    };
+
+    private final Handler mHandler = new Handler();
+    private final Context mContext;
+
+    private final SensorManager mSensorManager;
+    private final DataCollector mDataCollector;
+    private final HumanInteractionClassifier mHumanInteractionClassifier;
+
+    private static FalsingManager sInstance = null;
+
+    private boolean mEnforceBouncer = false;
+    private boolean mBouncerOn = false;
+    private boolean mSessionActive = false;
+    private int mState = StatusBarState.SHADE;
+
+    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateConfiguration();
+        }
+    };
+
+    private FalsingManager(Context context) {
+        mContext = context;
+        mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+        mDataCollector = DataCollector.getInstance(mContext);
+        mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
+                mSettingsObserver,
+                UserHandle.USER_ALL);
+
+        updateConfiguration();
+    }
+
+    public static FalsingManager getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new FalsingManager(context);
+        }
+        return sInstance;
+    }
+
+    private void updateConfiguration() {
+        mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
+                ENFORCE_BOUNCER, 0);
+    }
+
+    private boolean sessionEntrypoint() {
+        if (!mSessionActive && isEnabled() &&
+                (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
+            onSessionStart();
+            return true;
+        }
+        return false;
+    }
+
+    private void sessionExitpoint() {
+        if (mSessionActive) {
+            mSessionActive = false;
+            mSensorManager.unregisterListener(this);
+        }
+    }
+
+    private void onSessionStart() {
+        mBouncerOn = false;
+        mSessionActive = true;
+        for (int sensorType : SENSORS) {
+            Sensor s = mSensorManager.getDefaultSensor(sensorType);
+            if (s != null) {
+                mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
+            }
+        }
+    }
+
+    private boolean isEnabled() {
+        return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
+    }
+
+    /**
+     * @param type the type of action for which this method is called
+     * @return true if the classifier determined that this is not a human interacting with the phone
+     */
+    public boolean isFalseTouch(int type) {
+        return mHumanInteractionClassifier.getFalseTouchEvaluation(type) > 0.5;
+    }
+
+    @Override
+    public synchronized void onSensorChanged(SensorEvent event) {
+        mDataCollector.onSensorChanged(event);
+        mHumanInteractionClassifier.onSensorChanged(event);
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        mDataCollector.onAccuracyChanged(sensor, accuracy);
+    }
+
+    public boolean shouldEnforceBouncer() {
+        return mEnforceBouncer;
+    }
+
+    public void setStatusBarState(int state) {
+        mState = state;
+    }
+
+    public void onScreenTurningOn() {
+        if (sessionEntrypoint()) {
+            mDataCollector.onScreenTurningOn();
+        }
+    }
+
+    public void onScreenOnFromTouch() {
+        if (sessionEntrypoint()) {
+            mDataCollector.onScreenOnFromTouch();
+        }
+    }
+
+    public void onScreenOff() {
+        mDataCollector.onScreenOff();
+        sessionExitpoint();
+    }
+
+    public void onSucccessfulUnlock() {
+        mDataCollector.onSucccessfulUnlock();
+        sessionExitpoint();
+    }
+
+    public void onBouncerShown() {
+        if (!mBouncerOn) {
+            mBouncerOn = true;
+            mDataCollector.onBouncerShown();
+        }
+    }
+
+    public void onBouncerHidden() {
+        if (mBouncerOn) {
+            mBouncerOn = false;
+            mDataCollector.onBouncerHidden();
+        }
+    }
+
+    public void onQsDown() {
+        mDataCollector.onQsDown();
+    }
+
+    public void setQsExpanded(boolean expanded) {
+        mDataCollector.setQsExpanded(expanded);
+    }
+
+    public void onTrackingStarted() {
+        mDataCollector.onTrackingStarted();
+    }
+
+    public void onTrackingStopped() {
+        mDataCollector.onTrackingStopped();
+    }
+
+    public void onNotificationActive() {
+        mDataCollector.onNotificationActive();
+    }
+
+    public void onNotificationDoubleTap() {
+        mDataCollector.onNotificationDoubleTap();
+    }
+
+    public void setNotificationExpanded() {
+        mDataCollector.setNotificationExpanded();
+    }
+
+    public void onNotificatonStartDraggingDown() {
+        mDataCollector.onNotificatonStartDraggingDown();
+    }
+
+    public void onNotificatonStopDraggingDown() {
+        mDataCollector.onNotificatonStopDraggingDown();
+    }
+
+    public void onNotificationDismissed() {
+        mDataCollector.onNotificationDismissed();
+    }
+
+    public void onNotificatonStartDismissing() {
+        mDataCollector.onNotificatonStartDismissing();
+    }
+
+    public void onNotificatonStopDismissing() {
+        mDataCollector.onNotificatonStopDismissing();
+    }
+
+    public void onCameraOn() {
+        mDataCollector.onCameraOn();
+    }
+
+    public void onLeftAffordanceOn() {
+        mDataCollector.onLeftAffordanceOn();
+    }
+
+    public void onAffordanceSwipingStarted(boolean rightCorner) {
+        mDataCollector.onAffordanceSwipingStarted(rightCorner);
+    }
+
+    public void onAffordanceSwipingAborted() {
+        mDataCollector.onAffordanceSwipingAborted();
+    }
+
+    public void onUnlockHintStarted() {
+        mDataCollector.onUnlockHintStarted();
+    }
+
+    public void onCameraHintStarted() {
+        mDataCollector.onCameraHintStarted();
+    }
+
+    public void onLeftAffordanceHintStarted() {
+        mDataCollector.onLeftAffordanceHintStarted();
+    }
+
+    public void onTouchEvent(MotionEvent event, int width, int height) {
+        if (mSessionActive && !mBouncerOn) {
+            mDataCollector.onTouchEvent(event, width, height);
+            mHumanInteractionClassifier.onTouchEvent(event);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
new file mode 100644
index 0000000..a5f6df85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.SensorEvent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.MotionEvent;
+
+/**
+ * An classifier trying to determine whether it is a human interacting with the phone or not.
+ */
+public class HumanInteractionClassifier extends Classifier {
+    private static final String HIC_ENABLE = "HIC_enable";
+    private static HumanInteractionClassifier sInstance = null;
+
+    private final Handler mHandler = new Handler();
+    private final Context mContext;
+
+    private AnglesVarianceClassifier mAnglesVarianceClassifier;
+    private boolean mEnableClassifier = false;
+
+    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateConfiguration();
+        }
+    };
+
+    private HumanInteractionClassifier(Context context) {
+        mContext = context;
+        mClassifierData = new ClassifierData();
+        mAnglesVarianceClassifier = new AnglesVarianceClassifier(mClassifierData);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(HIC_ENABLE), false,
+                mSettingsObserver,
+                UserHandle.USER_ALL);
+
+        updateConfiguration();
+    }
+
+    public static HumanInteractionClassifier getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new HumanInteractionClassifier(context);
+        }
+        return sInstance;
+    }
+
+    private void updateConfiguration() {
+        mEnableClassifier = Build.IS_DEBUGGABLE && 0 != Settings.Global.getInt(
+                mContext.getContentResolver(),
+                HIC_ENABLE, 0);
+    }
+
+    @Override
+    public void onTouchEvent(MotionEvent event) {
+        if (mEnableClassifier) {
+            mClassifierData.update(event);
+            mAnglesVarianceClassifier.onTouchEvent(event);
+            mClassifierData.cleanUp(event);
+        }
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+    }
+
+    @Override
+    public float getFalseTouchEvaluation(int type) {
+        if (mEnableClassifier) {
+            return mAnglesVarianceClassifier.getFalseTouchEvaluation(type);
+        }
+        return 0.0f;
+    }
+
+    public boolean isEnabled() {
+        return mEnableClassifier;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Point.java b/packages/SystemUI/src/com/android/systemui/classifier/Point.java
new file mode 100644
index 0000000..e7dbae1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Point.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+public class Point {
+    public float x;
+    public float y;
+    public long timeOffsetNano;
+
+    public Point(float x, float y) {
+        this.x = x;
+        this.y = y;
+        this.timeOffsetNano = 0;
+    }
+
+    public Point(float x, float y, long timeOffsetNano) {
+        this.x = x;
+        this.y = y;
+        this.timeOffsetNano = timeOffsetNano;
+    }
+
+    public boolean equals(Point p) {
+        return x == p.x && y == p.y;
+    }
+
+    public float dist(Point a) {
+        return (float) Math.hypot(a.x - x, a.y - y);
+    }
+
+    /**
+     * Calculates the cross product of vec(this, a) and vec(this, b) where vec(x,y) is the
+     * vector from point x to point y
+     */
+    public float crossProduct(Point a, Point b) {
+        return (a.x - x) * (b.y - y) - (a.y - y) * (b.x - x);
+    }
+
+    /**
+     * Calculates the dot product of vec(this, a) and vec(this, b) where vec(x,y) is the
+     * vector from point x to point y
+     */
+    public float dotProduct(Point a, Point b) {
+        return (a.x - x) * (b.x - x) + (a.y - y) * (b.y - y);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
new file mode 100644
index 0000000..f386cbe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.classifier;
+
+import java.util.ArrayList;
+
+/**
+ * Contains data about movement traces (pointers)
+ */
+public class Stroke {
+    private ArrayList<Point> mPoints = new ArrayList<>();
+    private long mStartTimeNano;
+    private long mEndTimeNano;
+
+    public Stroke(long eventTimeNano) {
+        mStartTimeNano = mEndTimeNano = eventTimeNano;
+    }
+
+    public void addPoint(float x, float y, long eventTimeNano) {
+        mEndTimeNano = eventTimeNano;
+        mPoints.add(new Point(x, y, eventTimeNano - mStartTimeNano));
+    }
+
+    public ArrayList<Point> getPoints() {
+        return mPoints;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3514c5d..84e5d09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -68,8 +68,8 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SystemUI;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.FingerprintUnlockController;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -1244,7 +1244,7 @@
                 case START_KEYGUARD_EXIT_ANIM:
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
-                    LockedPhoneAnalytics.getInstance(mContext).onSucccessfulUnlock();
+                    FalsingManager.getInstance(mContext).onSucccessfulUnlock();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
                     Log.w(TAG, "Timeout while waiting for activity drawn!");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index c1dfec3..2e3e00c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -34,7 +34,7 @@
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
+import com.android.systemui.classifier.FalsingManager;
 
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
@@ -129,7 +129,7 @@
     private final int mNormalColor;
     private final int mLowPriorityColor;
     private boolean mIsBelowSpeedBump;
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -153,7 +153,7 @@
                 R.color.notification_ripple_color_low_priority);
         mNormalRippleColor = context.getColor(
                 R.color.notification_ripple_untinted_color);
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     @Override
@@ -222,7 +222,7 @@
                         makeActive();
                         postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                     } else {
-                        mLockedPhoneAnalytics.onNotificationDoubleTap();
+                        mFalsingManager.onNotificationDoubleTap();
                         boolean performed = performClick();
                         if (performed) {
                             removeCallbacks(mTapTimeoutRunnable);
@@ -242,7 +242,7 @@
     }
 
     private void makeActive() {
-        mLockedPhoneAnalytics.onNotificationActive();
+        mFalsingManager.onNotificationActive();
         startActivateAnimation(false /* reverse */);
         mActivated = true;
         if (mOnActivatedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index e2304c1..d912795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -29,7 +29,7 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
+import com.android.systemui.classifier.FalsingManager;
 
 /**
  * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
@@ -55,7 +55,7 @@
     private ExpandableView mStartingChild;
     private Interpolator mInterpolator;
     private float mLastHeight;
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
             DragDownCallback dragDownCallback) {
@@ -67,7 +67,7 @@
         mCallback = callback;
         mDragDownCallback = dragDownCallback;
         mHost = host;
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     @Override
@@ -87,7 +87,7 @@
             case MotionEvent.ACTION_MOVE:
                 final float h = y - mInitialTouchY;
                 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
-                    mLockedPhoneAnalytics.onNotificatonStartDraggingDown();
+                    mFalsingManager.onNotificatonStartDraggingDown();
                     mDraggingDown = true;
                     captureStartingChild(mInitialTouchX, mInitialTouchY);
                     mInitialTouchY = y;
@@ -205,7 +205,7 @@
     }
 
     private void stopDragging() {
-        mLockedPhoneAnalytics.onNotificatonStopDraggingDown();
+        mFalsingManager.onNotificatonStopDraggingDown();
         if (mStartingChild != null) {
             cancelExpansion(mStartingChild);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 564a60a..210be9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -24,7 +24,6 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -35,7 +34,7 @@
 import android.widget.ImageView;
 
 import com.android.systemui.R;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
@@ -111,7 +110,7 @@
                     !mChildrenExpanded);
         }
     };
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     private boolean mJustClicked;
 
@@ -327,7 +326,7 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     /**
@@ -515,7 +514,7 @@
      * @param userExpanded whether the user wants this notification to be expanded
      */
     public void setUserExpanded(boolean userExpanded) {
-        mLockedPhoneAnalytics.setNotificationExpanded();
+        mFalsingManager.setNotificationExpanded();
         if (userExpanded && !mExpandable) return;
         final boolean wasExpanded = isExpanded();
         mHasUserChangedExpansion = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index cbd23bb..99436a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -31,7 +31,7 @@
 import com.android.keyguard.R;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
+import com.android.systemui.classifier.FalsingManager;
 
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -50,7 +50,7 @@
     private ViewGroup mRoot;
     private boolean mShowingSoon;
     private int mBouncerPromptReason;
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -68,11 +68,11 @@
         mContainer = container;
         mWindowManager = windowManager;
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(mContext);
+        mFalsingManager = FalsingManager.getInstance(mContext);
     }
 
     public void show(boolean resetSecuritySelection) {
-        mLockedPhoneAnalytics.onBouncerShown();
+        mFalsingManager.onBouncerShown();
         ensureView();
         if (resetSecuritySelection) {
             // showPrimarySecurityScreen() updates the current security method. This is needed in
@@ -132,7 +132,7 @@
     }
 
     public void hide(boolean destroyView) {
-        mLockedPhoneAnalytics.onBouncerHidden();
+        mFalsingManager.onBouncerHidden();
         cancelShowRunnable();
          if (mKeyguardView != null) {
             mKeyguardView.cancelDismissAction();
@@ -162,7 +162,7 @@
     public void reset() {
         cancelShowRunnable();
         inflateView();
-        mLockedPhoneAnalytics.onBouncerHidden();
+        mFalsingManager.onBouncerHidden();
     }
 
     public void onScreenTurnedOff() {
@@ -250,7 +250,7 @@
 
             // We need to show it in case it is secure. If not, it will get dismissed in any case.
             mRoot.setVisibility(View.VISIBLE);
-            mLockedPhoneAnalytics.onBouncerShown();
+            mFalsingManager.onBouncerShown();
             mKeyguardView.requestFocus();
             mKeyguardView.onResume();
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index f47ec20..980527b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -22,7 +22,6 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -50,6 +49,7 @@
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -59,7 +59,6 @@
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -204,7 +203,7 @@
     private boolean mClosingWithAlphaFadeOut;
     private boolean mHeadsUpAnimatingAway;
     private boolean mLaunchingAffordance;
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
         @Override
@@ -221,7 +220,7 @@
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     public void setStatusBar(PhoneStatusBar bar) {
@@ -813,7 +812,7 @@
     private void handleQsDown(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
-            mLockedPhoneAnalytics.onQsDown();
+            mFalsingManager.onQsDown();
             mQsTracking = true;
             onQsExpansionStarted();
             mInitialHeightOnTouch = mQsExpansionHeight;
@@ -981,7 +980,7 @@
             mQsExpanded = expanded;
             updateQsState();
             requestPanelHeightUpdate();
-            mLockedPhoneAnalytics.setQsExpanded(expanded);
+            mFalsingManager.setQsExpanded(expanded);
             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
             mStatusBar.setQsExpanded(expanded);
             mQsPanel.setExpanded(expanded);
@@ -1308,7 +1307,7 @@
                     R.string.accessibility_desc_quick_settings));
             mLastAnnouncementWasQuickSettings = true;
         }
-        if (mQsFullyExpanded && mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+        if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) {
             mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
                     false /* dismissShade */, true /* afterKeyguardGone */);
         }
@@ -1839,7 +1838,7 @@
 
     @Override
     protected void onTrackingStarted() {
-        mLockedPhoneAnalytics.onTrackingStarted();
+        mFalsingManager.onTrackingStarted();
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
@@ -1853,7 +1852,7 @@
 
     @Override
     protected void onTrackingStopped(boolean expand) {
-        mLockedPhoneAnalytics.onTrackingStopped();
+        mFalsingManager.onTrackingStopped();
         super.onTrackingStopped(expand);
         if (expand) {
             mNotificationStackScroller.setOverScrolledPixels(
@@ -1953,8 +1952,8 @@
             EventLogTags.writeSysuiLockscreenGesture(
                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
 
-            mLockedPhoneAnalytics.onLeftAffordanceOn();
-            if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+            mFalsingManager.onLeftAffordanceOn();
+            if (mFalsingManager.shouldEnforceBouncer()) {
                 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
                     @Override
                     public void run() {
@@ -1969,8 +1968,8 @@
             EventLogTags.writeSysuiLockscreenGesture(
                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
 
-            mLockedPhoneAnalytics.onCameraOn();
-            if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+            mFalsingManager.onCameraOn();
+            if (mFalsingManager.shouldEnforceBouncer()) {
                 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
                     @Override
                     public void run() {
@@ -2024,7 +2023,7 @@
 
     @Override
     public void onSwipingStarted(boolean rightIcon) {
-        mLockedPhoneAnalytics.onAffordanceSwipingStarted(rightIcon);
+        mFalsingManager.onAffordanceSwipingStarted(rightIcon);
         boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
                 : rightIcon;
         if (camera) {
@@ -2037,7 +2036,7 @@
 
     @Override
     public void onSwipingAborted() {
-        mLockedPhoneAnalytics.onAffordanceSwipingAborted();
+        mFalsingManager.onAffordanceSwipingAborted();
         mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 9cd6ea3..13d0e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -35,6 +35,9 @@
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
+import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.classifier.HumanInteractionClassifier;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
@@ -85,6 +88,7 @@
     private ObjectAnimator mPeekAnimator;
     private VelocityTrackerInterface mVelocityTracker;
     private FlingAnimationUtils mFlingAnimationUtils;
+    private FalsingManager mFalsingManager;
 
     /**
      * Whether an instant expand request is currently pending and we are just waiting for layout.
@@ -190,6 +194,7 @@
         mLinearOutSlowInInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
         mBounceInterpolator = new BounceInterpolator();
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     protected void loadDimens() {
@@ -605,6 +610,9 @@
         if (!mStatusBar.isFalsingThresholdNeeded()) {
             return false;
         }
+        if (mFalsingManager.isFalseTouch(Classifier.UNLOCK)) {
+            return true;
+        }
         if (!mTouchAboveFalsingThreshold) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 33ebfff..ca16567 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -108,6 +108,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
@@ -131,7 +132,6 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -598,7 +598,7 @@
     private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     @Override
     public void start() {
@@ -646,7 +646,7 @@
         notifyUserAboutHiddenNotifications();
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(mContext);
+        mFalsingManager = FalsingManager.getInstance(mContext);
     }
 
     // ================================================================================
@@ -3805,7 +3805,7 @@
         }
         mState = state;
         mGroupManager.setStatusBarState(state);
-        mLockedPhoneAnalytics.setStatusBarState(state);
+        mFalsingManager.setStatusBarState(state);
         mStatusBarWindowManager.setStatusBarState(state);
         updateDozing();
     }
@@ -3827,7 +3827,7 @@
     }
 
     public void onUnlockHintStarted() {
-        mLockedPhoneAnalytics.onUnlockHintStarted();
+        mFalsingManager.onUnlockHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
     }
 
@@ -3837,17 +3837,17 @@
     }
 
     public void onCameraHintStarted() {
-        mLockedPhoneAnalytics.onCameraHintStarted();
+        mFalsingManager.onCameraHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
     }
 
     public void onVoiceAssistHintStarted() {
-        mLockedPhoneAnalytics.onLeftAffordanceHintStarted();
+        mFalsingManager.onLeftAffordanceHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
     }
 
     public void onPhoneHintStarted() {
-        mLockedPhoneAnalytics.onLeftAffordanceHintStarted();
+        mFalsingManager.onLeftAffordanceHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
     }
 
@@ -3922,7 +3922,7 @@
             row.setUserExpanded(true);
         }
         boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
-                || !mShowLockscreenNotifications || mLockedPhoneAnalytics.shouldEnforceBouncer();
+                || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer();
         if (isLockscreenPublicMode() && fullShadeNeedsBouncer) {
             mLeaveOpenOnKeyguardHide = true;
             showBouncer();
@@ -3970,7 +3970,7 @@
         mDeviceInteractive = false;
         mWakeUpComingFromTouch = false;
         mWakeUpTouchLocation = null;
-        mLockedPhoneAnalytics.onScreenOff();
+        mFalsingManager.onScreenOff();
         mStackScroller.setAnimationsEnabled(false);
         updateVisibleToUser();
     }
@@ -3980,11 +3980,11 @@
         mStackScroller.setAnimationsEnabled(true);
         mNotificationPanel.setTouchDisabled(false);
         updateVisibleToUser();
-        mLockedPhoneAnalytics.onScreenOn();
     }
 
     public void onScreenTurningOn() {
         mScreenTurningOn = true;
+        mFalsingManager.onScreenTurningOn();
         mNotificationPanel.onScreenTurningOn();
         if (mLaunchCameraOnScreenTurningOn) {
             mNotificationPanel.launchCamera(false);
@@ -4119,7 +4119,7 @@
             mWakeUpTouchLocation = new PointF(event.getX(), event.getY());
             mNotificationPanel.setTouchDisabled(false);
             mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
-            mLockedPhoneAnalytics.onScreenOnFromTouch();
+            mFalsingManager.onScreenOnFromTouch();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index bbf981f..cfd3358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -36,10 +36,10 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 
@@ -56,14 +56,14 @@
 
     private PhoneStatusBar mService;
     private final Paint mTransparentSrcPaint = new Paint();
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setMotionEventSplittingEnabled(false);
         mTransparentSrcPaint.setColor(0);
         mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     @Override
@@ -200,7 +200,7 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        mLockedPhoneAnalytics.onTouchEvent(ev, getWidth(), getHeight());
+        mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
         if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) {
             // Disallow new pointers while the brightness mirror is visible. This is so that you
             // can't touch anything other than the brightness slider while the mirror is showing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 51c2208..5e5f810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -47,7 +48,6 @@
 import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.analytics.LockedPhoneAnalytics;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -232,7 +232,7 @@
     private boolean mForceNoOverlappingRendering;
     private NotificationOverflowContainer mOverflowContainer;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
-    private LockedPhoneAnalytics mLockedPhoneAnalytics;
+    private FalsingManager mFalsingManager;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -266,7 +266,7 @@
             mDebugPaint.setStrokeWidth(2);
             mDebugPaint.setStyle(Paint.Style.STROKE);
         }
-        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
+        mFalsingManager = FalsingManager.getInstance(context);
     }
 
     @Override
@@ -599,8 +599,8 @@
         }
         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
 
-        mLockedPhoneAnalytics.onNotificationDismissed();
-        if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+        mFalsingManager.onNotificationDismissed();
+        if (mFalsingManager.shouldEnforceBouncer()) {
             mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
                     false /* dismissShade */, true /* afterKeyguardGone */);
         }
@@ -631,7 +631,7 @@
     }
 
     public void onBeginDrag(View v) {
-        mLockedPhoneAnalytics.onNotificatonStartDismissing();
+        mFalsingManager.onNotificatonStartDismissing();
         setSwipingInProgress(true);
         mAmbientState.onBeginDrag(v);
         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
@@ -658,7 +658,7 @@
     }
 
     public void onDragCancelled(View v) {
-        mLockedPhoneAnalytics.onNotificatonStopDismissing();
+        mFalsingManager.onNotificatonStopDismissing();
         setSwipingInProgress(false);
     }