System to track touch and sensor events before unlocking the phone

Added a new component which tracks touch and sensor events and
also events like showing the bouncer tapping a notification and
others. The collection is enabled when the screen is turned on and
is disabled after the phone is unlocked. The data is saved in a
protobuf file in internal storage in a directory called
"good_touches". There is also an option to collect events which
end with the screen turning off. These are saved in the
"bad_touches" file. Everything is hidden behind the
ENABLE_ANALYTICS flag which is set by default to false and can be
turned on only if Build.IS_DEBUGGABLE is true. Also
behind the ENFORCE_BOUNCER flag the class shows the bouncer before
expanding a notification, showing quick settings or launching an
affordance from one of the bottom corners.

Change-Id: Iaeae0fb7a0d9c707daf7a270201fa5b1cd84c74a
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 51fea2a..314b3c4 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -3,13 +3,15 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) \
     src/com/android/systemui/EventLogTags.logtags
 
 LOCAL_STATIC_JAVA_LIBRARIES := Keyguard
 LOCAL_JAVA_LIBRARIES := telephony-common
 
 LOCAL_PACKAGE_NAME := SystemUI
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 LOCAL_CERTIFICATE := platform
 LOCAL_PRIVILEGED_MODULE := true
 
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java b/packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java
new file mode 100644
index 0000000..e458862
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java
@@ -0,0 +1,453 @@
+/*
+ * 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.analytics;
+
+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.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+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;
+
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.PhoneEvent;
+
+/**
+ * Tracks touch, sensor and phone events when the lockscreen is on. If the phone is unlocked
+ * the data containing these events is saved to a file. This data is collected
+ * to analyze how a human interaction looks like.
+ *
+ * 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";
+
+    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();
+    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateConfiguration();
+        }
+    };
+
+    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);
+        mContext = context;
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(ANALYTICS_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,
+                mSettingsObserver,
+                UserHandle.USER_ALL);
+
+        updateConfiguration();
+    }
+
+    public static LockedPhoneAnalytics getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new LockedPhoneAnalytics(context);
+        }
+        return sInstance;
+    }
+
+    private void updateConfiguration() {
+        mEnableAnalytics = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                ANALYTICS_ENABLE, 0);
+        mEnforceBouncer = mEnableAnalytics && 0 != Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                ENFORCE_BOUNCER, 0);
+        mCollectBadTouches = mEnableAnalytics && 0 != Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                COLLECT_BAD_TOCUHES, 0);
+    }
+
+    private boolean sessionEntrypoint() {
+        if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)
+                && mEnableAnalytics && mCurrentSession == null) {
+            onSessionStart();
+            return true;
+        }
+        return false;
+    }
+
+    private void sessionExitpoint(int result) {
+        if (mEnableAnalytics && 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) {
+        SensorLoggerSession session = mCurrentSession;
+        mCurrentSession = null;
+
+        session.end(System.currentTimeMillis(), result);
+        queueSession(session);
+    }
+
+    private void queueSession(final SensorLoggerSession currentSession) {
+        AsyncTask.execute(new Runnable() {
+            @Override
+            public void run() {
+                byte[] b = Session.toByteArray(currentSession.toProto());
+                String dir = mContext.getFilesDir().getAbsolutePath();
+                if (currentSession.getResult() != Session.SUCCESS) {
+                    if (!mCollectBadTouches) {
+                        return;
+                    }
+                    dir += "/bad_touches";
+                } else {
+                    dir += "/good_touches";
+                }
+
+                File file = new File(dir);
+                file.mkdir();
+                File touch = new File(file, "trace_" + System.currentTimeMillis());
+
+                try {
+                    new FileOutputStream(touch).write(b);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+    }
+
+
+    @Override
+    public synchronized void onSensorChanged(SensorEvent event) {
+        if (mEnableAnalytics && mCurrentSession != null) {
+            mCurrentSession.addSensorEvent(event, System.nanoTime());
+            enforceTimeout();
+        }
+    }
+
+    private void enforceTimeout() {
+        if (mTimeoutActive) {
+            if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis()
+                    > TIMEOUT_MILLIS) {
+                onSessionEnd(Session.UNKNOWN);
+                if (DEBUG) {
+                    Log.i(TAG, "Analytics timed out.");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    public boolean shouldEnforceBouncer() {
+        return mEnforceBouncer;
+    }
+
+    public void setStatusBarState(int state) {
+        mState = state;
+    }
+
+    public void onScreenOn() {
+        if (sessionEntrypoint()) {
+            if (DEBUG) {
+                Log.d(TAG, "onScreenOn");
+            }
+            addEvent(PhoneEvent.ON_SCREEN_ON);
+        }
+    }
+
+    public void onScreenOnFromTouch() {
+        if (sessionEntrypoint()) {
+            if (DEBUG) {
+                Log.d(TAG, "onScreenOnFromTouch");
+            }
+            addEvent(PhoneEvent.ON_SCREEN_ON_FROM_TOUCH);
+        }
+    }
+
+    public void onScreenOff() {
+        if (DEBUG) {
+            Log.d(TAG, "onScreenOff");
+        }
+        addEvent(PhoneEvent.ON_SCREEN_OFF);
+        sessionExitpoint(Session.FAILURE);
+    }
+
+    public void onSucccessfulUnlock() {
+        if (DEBUG) {
+            Log.d(TAG, "onSuccessfulUnlock");
+        }
+        addEvent(PhoneEvent.ON_SUCCESSFUL_UNLOCK);
+        sessionExitpoint(Session.SUCCESS);
+    }
+
+    public void onBouncerShown() {
+        if (!mBouncerOn) {
+            if (DEBUG) {
+                Log.d(TAG, "onBouncerShown");
+            }
+            mBouncerOn = true;
+            addEvent(PhoneEvent.ON_BOUNCER_SHOWN);
+        }
+    }
+
+    public void onBouncerHidden() {
+        if (mBouncerOn) {
+            if (DEBUG) {
+                Log.d(TAG, "onBouncerHidden");
+            }
+            mBouncerOn = false;
+            addEvent(PhoneEvent.ON_BOUNCER_HIDDEN);
+        }
+    }
+
+    public void onQsDown() {
+        if (DEBUG) {
+            Log.d(TAG, "onQsDown");
+        }
+        addEvent(PhoneEvent.ON_QS_DOWN);
+    }
+
+    public void setQsExpanded(boolean expanded) {
+        if (DEBUG) {
+            Log.d(TAG, "setQsExpanded = " + expanded);
+        }
+        if (expanded) {
+            addEvent(PhoneEvent.SET_QS_EXPANDED_TRUE);
+        } else {
+            addEvent(PhoneEvent.SET_QS_EXPANDED_FALSE);
+        }
+    }
+
+    public void onTrackingStarted() {
+        if (DEBUG) {
+            Log.d(TAG, "onTrackingStarted");
+        }
+        mTrackingStarted = true;
+        addEvent(PhoneEvent.ON_TRACKING_STARTED);
+    }
+
+    public void onTrackingStopped() {
+        if (mTrackingStarted) {
+            if (DEBUG) {
+                Log.d(TAG, "onTrackingStopped");
+            }
+            mTrackingStarted = false;
+            addEvent(PhoneEvent.ON_TRACKING_STOPPED);
+        }
+    }
+
+    public void onNotificationActive() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationActive");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_ACTIVE);
+    }
+
+
+    public void onNotificationDoubleTap() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationDoubleTap");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_DOUBLE_TAP);
+    }
+
+    public void setNotificationExpanded() {
+        if (DEBUG) {
+            Log.d(TAG, "setNotificationExpanded");
+        }
+        addEvent(PhoneEvent.SET_NOTIFICATION_EXPANDED);
+    }
+
+    public void onNotificatonStartDraggingDown() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationStartDraggingDown");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_START_DRAGGING_DOWN);
+    }
+
+    public void onNotificatonStopDraggingDown() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationStopDraggingDown");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_STOP_DRAGGING_DOWN);
+    }
+
+    public void onNotificationDismissed() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationDismissed");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_DISMISSED);
+    }
+
+    public void onNotificatonStartDismissing() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationStartDismissing");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_START_DISMISSING);
+    }
+
+    public void onNotificatonStopDismissing() {
+        if (DEBUG) {
+            Log.d(TAG, "onNotificationStopDismissing");
+        }
+        addEvent(PhoneEvent.ON_NOTIFICATION_STOP_DISMISSING);
+    }
+
+    public void onCameraOn() {
+        if (DEBUG) {
+            Log.d(TAG, "onCameraOn");
+        }
+        addEvent(PhoneEvent.ON_CAMERA_ON);
+    }
+
+    public void onLeftAffordanceOn() {
+        if (DEBUG) {
+            Log.d(TAG, "onLeftAffordanceOn");
+        }
+        addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_ON);
+    }
+
+    public void onAffordanceSwipingStarted(boolean rightCorner) {
+        if (DEBUG) {
+            Log.d(TAG, "onAffordanceSwipingStarted");
+        }
+        mCornerSwiping = true;
+        if (rightCorner) {
+            addEvent(PhoneEvent.ON_RIGHT_AFFORDANCE_SWIPING_STARTED);
+        } else {
+            addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_SWIPING_STARTED);
+        }
+    }
+
+    public void onAffordanceSwipingAborted() {
+        if (mCornerSwiping) {
+            if (DEBUG) {
+                Log.d(TAG, "onAffordanceSwipingAborted");
+            }
+            mCornerSwiping = false;
+            addEvent(PhoneEvent.ON_AFFORDANCE_SWIPING_ABORTED);
+        }
+    }
+
+    public void onUnlockHintStarted() {
+        if (DEBUG) {
+            Log.d(TAG, "onUnlockHintStarted");
+        }
+        addEvent(PhoneEvent.ON_UNLOCK_HINT_STARTED);
+    }
+
+    public void onCameraHintStarted() {
+        if (DEBUG) {
+            Log.d(TAG, "onCameraHintStarted");
+        }
+        addEvent(PhoneEvent.ON_CAMERA_HINT_STARTED);
+    }
+
+    public void onLeftAffordanceHintStarted() {
+        if (DEBUG) {
+            Log.d(TAG, "onLeftAffordanceHintStarted");
+        }
+        addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_HINT_STARTED);
+    }
+
+    public void onTouchEvent(MotionEvent ev, int width, int height) {
+        if (!mBouncerOn && mCurrentSession != null) {
+            if (DEBUG) {
+                Log.v(TAG, "onTouchEvent(ev.action="
+                        + MotionEvent.actionToString(ev.getAction()) + ")");
+            }
+            mCurrentSession.addMotionEvent(ev);
+            mCurrentSession.setTouchArea(width, height);
+            enforceTimeout();
+        }
+    }
+
+    private void addEvent(int eventType) {
+        if (mEnableAnalytics && 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
new file mode 100644
index 0000000..09383f6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
@@ -0,0 +1,160 @@
+/*
+ * 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.analytics;
+
+import android.os.Build;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.TouchEvent;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.SensorEvent;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.PhoneEvent;
+
+/**
+ * Collects touch, sensor and phone events and converts the data to
+ * TouchAnalyticsProto.Session.
+ */
+public class SensorLoggerSession {
+    private static final String TAG = "SensorLoggerSession";
+
+    private final long mStartTimestampMillis;
+    private final long mStartSystemTimeNanos;
+
+    private long mEndTimestampMillis;
+    private int mType;
+
+    private ArrayList<TouchEvent> mMotionEvents = new ArrayList<>();
+    private ArrayList<SensorEvent> mSensorEvents = new ArrayList<>();
+    private ArrayList<PhoneEvent> mPhoneEvents = new ArrayList<>();
+    private int mTouchAreaHeight;
+    private int mTouchAreaWidth;
+    private int mResult = Session.UNKNOWN;
+
+    public SensorLoggerSession(long startTimestampMillis, long startSystemTimeNanos) {
+        mStartTimestampMillis = startTimestampMillis;
+        mStartSystemTimeNanos = startSystemTimeNanos;
+        mType = Session.REAL;
+    }
+
+    public void end(long endTimestampMillis, int result) {
+        mResult = result;
+        mEndTimestampMillis = endTimestampMillis;
+
+        if (LockedPhoneAnalytics.DEBUG) {
+            Log.d(TAG, "Ending session result=" + result + " it lasted for " +
+                    (float) (mEndTimestampMillis - mStartTimestampMillis) / 1000f + "s");
+        }
+    }
+
+    public void addMotionEvent(MotionEvent motionEvent) {
+        TouchEvent event = motionEventToProto(motionEvent);
+        mMotionEvents.add(event);
+    }
+
+    public void addSensorEvent(android.hardware.SensorEvent eventOrig, long systemTimeNanos) {
+        SensorEvent event = sensorEventToProto(eventOrig, systemTimeNanos);
+        mSensorEvents.add(event);
+    }
+
+    public void addPhoneEvent(int eventType, long systemTimeNanos) {
+        PhoneEvent event = phoneEventToProto(eventType, systemTimeNanos);
+        mPhoneEvents.add(event);
+    }
+
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Session{");
+        sb.append("mStartTimestampMillis=").append(mStartTimestampMillis);
+        sb.append(", mStartSystemTimeNanos=").append(mStartSystemTimeNanos);
+        sb.append(", mEndTimestampMillis=").append(mEndTimestampMillis);
+        sb.append(", mResult=").append(mResult);
+        sb.append(", mTouchAreaHeight=").append(mTouchAreaHeight);
+        sb.append(", mTouchAreaWidth=").append(mTouchAreaWidth);
+        sb.append(", mMotionEvents=[size=").append(mMotionEvents.size()).append("]");
+        sb.append(", mSensorEvents=[size=").append(mSensorEvents.size()).append("]");
+        sb.append(", mPhoneEvents=[size=").append(mPhoneEvents.size()).append("]");
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public Session toProto() {
+        Session proto = new Session();
+        proto.setStartTimestampMillis(mStartTimestampMillis);
+        proto.setDurationMillis(mEndTimestampMillis - mStartTimestampMillis);
+        proto.setBuild(Build.FINGERPRINT);
+        proto.setResult(mResult);
+        proto.setType(mType);
+        proto.sensorEvents = mSensorEvents.toArray(proto.sensorEvents);
+        proto.touchEvents = mMotionEvents.toArray(proto.touchEvents);
+        proto.phoneEvents = mPhoneEvents.toArray(proto.phoneEvents);
+        proto.setTouchAreaWidth(mTouchAreaWidth);
+        proto.setTouchAreaHeight(mTouchAreaHeight);
+        return proto;
+    }
+
+    private PhoneEvent phoneEventToProto(int eventType, long sysTimeNanos) {
+        PhoneEvent proto = new PhoneEvent();
+        proto.setType(eventType);
+        proto.setTimeOffsetNanos(sysTimeNanos - mStartSystemTimeNanos);
+        return proto;
+    }
+
+    private SensorEvent sensorEventToProto(android.hardware.SensorEvent ev, long sysTimeNanos) {
+        SensorEvent proto = new SensorEvent();
+        proto.setType(ev.sensor.getType());
+        proto.setTimeOffsetNanos(sysTimeNanos - mStartSystemTimeNanos);
+        proto.setTimestamp(ev.timestamp);
+        proto.values = ev.values.clone();
+        return proto;
+    }
+
+    private TouchEvent motionEventToProto(MotionEvent ev) {
+        int count = ev.getPointerCount();
+        TouchEvent proto = new TouchEvent();
+        proto.setTimeOffsetNanos(ev.getEventTimeNano() - mStartSystemTimeNanos);
+        proto.setAction(ev.getActionMasked());
+        proto.setActionIndex(ev.getActionIndex());
+        proto.pointers = new TouchEvent.Pointer[count];
+        for (int i = 0; i < count; i++) {
+            TouchEvent.Pointer p = new TouchEvent.Pointer();
+            p.setX(ev.getX(i));
+            p.setY(ev.getY(i));
+            p.setSize(ev.getSize(i));
+            p.setPressure(ev.getPressure(i));
+            p.setId(ev.getPointerId(i));
+            proto.pointers[i] = p;
+        }
+        return proto;
+    }
+
+    public void setTouchArea(int width, int height) {
+        mTouchAreaWidth = width;
+        mTouchAreaHeight = height;
+    }
+
+    public int getResult() {
+        return mResult;
+    }
+
+    public long getStartTimestampMillis() {
+        return mStartTimestampMillis;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index aafa991..a28e188 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -69,6 +69,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SystemUI;
 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;
@@ -1240,6 +1241,7 @@
                 case START_KEYGUARD_EXIT_ANIM:
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
+                    LockedPhoneAnalytics.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 7f17885..c1dfec3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -34,6 +34,7 @@
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
+import com.android.systemui.analytics.LockedPhoneAnalytics;
 
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
@@ -128,6 +129,7 @@
     private final int mNormalColor;
     private final int mLowPriorityColor;
     private boolean mIsBelowSpeedBump;
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -151,6 +153,7 @@
                 R.color.notification_ripple_color_low_priority);
         mNormalRippleColor = context.getColor(
                 R.color.notification_ripple_untinted_color);
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
     }
 
     @Override
@@ -219,6 +222,7 @@
                         makeActive();
                         postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                     } else {
+                        mLockedPhoneAnalytics.onNotificationDoubleTap();
                         boolean performed = performClick();
                         if (performed) {
                             removeCallbacks(mTapTimeoutRunnable);
@@ -238,6 +242,7 @@
     }
 
     private void makeActive() {
+        mLockedPhoneAnalytics.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 15a092c..e2304c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -29,6 +29,7 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.analytics.LockedPhoneAnalytics;
 
 /**
  * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
@@ -54,6 +55,7 @@
     private ExpandableView mStartingChild;
     private Interpolator mInterpolator;
     private float mLastHeight;
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
             DragDownCallback dragDownCallback) {
@@ -65,6 +67,7 @@
         mCallback = callback;
         mDragDownCallback = dragDownCallback;
         mHost = host;
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
     }
 
     @Override
@@ -84,6 +87,7 @@
             case MotionEvent.ACTION_MOVE:
                 final float h = y - mInitialTouchY;
                 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
+                    mLockedPhoneAnalytics.onNotificatonStartDraggingDown();
                     mDraggingDown = true;
                     captureStartingChild(mInitialTouchX, mInitialTouchY);
                     mInitialTouchY = y;
@@ -201,6 +205,7 @@
     }
 
     private void stopDragging() {
+        mLockedPhoneAnalytics.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 b88e5ca..6255967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -34,6 +34,7 @@
 import android.widget.ImageView;
 
 import com.android.systemui.R;
+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.stack.NotificationChildrenContainer;
@@ -109,6 +110,7 @@
                     !mChildrenExpanded);
         }
     };
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     public NotificationContentView getPrivateLayout() {
         return mPrivateLayout;
@@ -307,6 +309,7 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
     }
 
     /**
@@ -494,6 +497,7 @@
      * @param userExpanded whether the user wants this notification to be expanded
      */
     public void setUserExpanded(boolean userExpanded) {
+        mLockedPhoneAnalytics.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 8b96e5f..676f114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -31,6 +31,7 @@
 import com.android.keyguard.R;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DejankUtils;
+import com.android.systemui.analytics.LockedPhoneAnalytics;
 
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -49,6 +50,7 @@
     private ViewGroup mRoot;
     private boolean mShowingSoon;
     private int mBouncerPromptReason;
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -66,9 +68,11 @@
         mContainer = container;
         mWindowManager = windowManager;
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(mContext);
     }
 
     public void show(boolean resetSecuritySelection) {
+        mLockedPhoneAnalytics.onBouncerShown();
         ensureView();
         if (resetSecuritySelection) {
             // showPrimarySecurityScreen() updates the current security method. This is needed in
@@ -128,6 +132,7 @@
     }
 
     public void hide(boolean destroyView) {
+        mLockedPhoneAnalytics.onBouncerHidden();
         cancelShowRunnable();
          if (mKeyguardView != null) {
             mKeyguardView.cancelDismissAction();
@@ -157,6 +162,7 @@
     public void reset() {
         cancelShowRunnable();
         inflateView();
+        mLockedPhoneAnalytics.onBouncerHidden();
     }
 
     public void onScreenTurnedOff() {
@@ -244,6 +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();
             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 710c335..babca34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -56,6 +56,7 @@
 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;
@@ -203,6 +204,7 @@
     private int mLastOrientation = -1;
     private boolean mClosingWithAlphaFadeOut;
     private boolean mHeadsUpAnimatingAway;
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
         @Override
@@ -219,6 +221,7 @@
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
     }
 
     public void setStatusBar(PhoneStatusBar bar) {
@@ -820,6 +823,7 @@
     private void handleQsDown(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+            mLockedPhoneAnalytics.onQsDown();
             mQsTracking = true;
             onQsExpansionStarted();
             mInitialHeightOnTouch = mQsExpansionHeight;
@@ -987,6 +991,7 @@
             mQsExpanded = expanded;
             updateQsState();
             requestPanelHeightUpdate();
+            mLockedPhoneAnalytics.setQsExpanded(expanded);
             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
             mStatusBar.setQsExpanded(expanded);
             mQsPanel.setExpanded(expanded);
@@ -1313,6 +1318,10 @@
                     R.string.accessibility_desc_quick_settings));
             mLastAnnouncementWasQuickSettings = true;
         }
+        if (mQsFullyExpanded && mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+                    false /* dismissShade */, true /* afterKeyguardGone */);
+        }
         if (DEBUG) {
             invalidate();
         }
@@ -1840,6 +1849,7 @@
 
     @Override
     protected void onTrackingStarted() {
+        mLockedPhoneAnalytics.onTrackingStarted();
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
@@ -1853,6 +1863,7 @@
 
     @Override
     protected void onTrackingStopped(boolean expand) {
+        mLockedPhoneAnalytics.onTrackingStopped();
         super.onTrackingStopped(expand);
         if (expand) {
             mNotificationStackScroller.setOverScrolledPixels(
@@ -1951,11 +1962,35 @@
         if (start) {
             EventLogTags.writeSysuiLockscreenGesture(
                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
-            mKeyguardBottomArea.launchLeftAffordance();
+
+            mLockedPhoneAnalytics.onLeftAffordanceOn();
+            if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
+                    @Override
+                    public void run() {
+                        mKeyguardBottomArea.launchLeftAffordance();
+                    }
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */);
+            }
+            else {
+                mKeyguardBottomArea.launchLeftAffordance();
+            }
         } else {
             EventLogTags.writeSysuiLockscreenGesture(
                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
-            mSecureCameraLaunchManager.startSecureCameraLaunch();
+
+            mLockedPhoneAnalytics.onCameraOn();
+            if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
+                    @Override
+                    public void run() {
+                        mSecureCameraLaunchManager.startSecureCameraLaunch();
+                    }
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */);
+            }
+            else {
+                mSecureCameraLaunchManager.startSecureCameraLaunch();
+            }
         }
         mStatusBar.startLaunchTransitionTimeout();
         mBlockTouches = true;
@@ -1999,6 +2034,7 @@
 
     @Override
     public void onSwipingStarted(boolean rightIcon) {
+        mLockedPhoneAnalytics.onAffordanceSwipingStarted(rightIcon);
         boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
                 : rightIcon;
         if (camera) {
@@ -2012,6 +2048,7 @@
 
     @Override
     public void onSwipingAborted() {
+        mLockedPhoneAnalytics.onAffordanceSwipingAborted();
         mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
     }
 
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 b794353..4476192 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -130,6 +130,7 @@
 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;
@@ -588,6 +589,7 @@
     private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     @Override
     public void start() {
@@ -635,6 +637,7 @@
         notifyUserAboutHiddenNotifications();
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(mContext);
     }
 
     // ================================================================================
@@ -3755,6 +3758,7 @@
         }
         mState = state;
         mGroupManager.setStatusBarState(state);
+        mLockedPhoneAnalytics.setStatusBarState(state);
         mStatusBarWindowManager.setStatusBarState(state);
         updateDozing();
     }
@@ -3776,6 +3780,7 @@
     }
 
     public void onUnlockHintStarted() {
+        mLockedPhoneAnalytics.onUnlockHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
     }
 
@@ -3785,14 +3790,17 @@
     }
 
     public void onCameraHintStarted() {
+        mLockedPhoneAnalytics.onCameraHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
     }
 
     public void onVoiceAssistHintStarted() {
+        mLockedPhoneAnalytics.onLeftAffordanceHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
     }
 
     public void onPhoneHintStarted() {
+        mLockedPhoneAnalytics.onLeftAffordanceHintStarted();
         mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
     }
 
@@ -3867,7 +3875,7 @@
             row.setUserExpanded(true);
         }
         boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
-                || !mShowLockscreenNotifications;
+                || !mShowLockscreenNotifications || mLockedPhoneAnalytics.shouldEnforceBouncer();
         if (isLockscreenPublicMode() && fullShadeNeedsBouncer) {
             mLeaveOpenOnKeyguardHide = true;
             showBouncer();
@@ -3912,6 +3920,7 @@
         mDeviceInteractive = false;
         mWakeUpComingFromTouch = false;
         mWakeUpTouchLocation = null;
+        mLockedPhoneAnalytics.onScreenOff();
         mStackScroller.setAnimationsEnabled(false);
         updateVisibleToUser();
     }
@@ -3921,6 +3930,7 @@
         mStackScroller.setAnimationsEnabled(true);
         mNotificationPanel.setTouchDisabled(false);
         updateVisibleToUser();
+        mLockedPhoneAnalytics.onScreenOn();
     }
 
     public void onScreenTurningOn() {
@@ -4049,6 +4059,7 @@
             mWakeUpTouchLocation = new PointF(event.getX(), event.getY());
             mNotificationPanel.setTouchDisabled(false);
             mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
+            mLockedPhoneAnalytics.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 0e22aa8..bbf981f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -39,6 +39,7 @@
 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;
 
 
@@ -55,12 +56,14 @@
 
     private PhoneStatusBar mService;
     private final Paint mTransparentSrcPaint = new Paint();
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     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);
     }
 
     @Override
@@ -197,6 +200,7 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        mLockedPhoneAnalytics.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/phone/touch_analytics.proto b/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto
new file mode 100644
index 0000000..afc8f77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto
@@ -0,0 +1,136 @@
+/*
+ * 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
+ */
+
+syntax = "proto2";
+
+package systemui;
+
+option java_package = "com.android.systemui.statusbar.phone";
+option java_outer_classname = "TouchAnalyticsProto";
+
+message Session {
+    message TouchEvent {
+        message BoundingBox {
+            optional float width = 1;
+            optional float height = 2;
+        }
+
+        enum Action {
+            // Keep in sync with MotionEvent.
+            DOWN = 0;
+            UP = 1;
+            MOVE = 2;
+            CANCEL = 3;
+            OUTSIDE = 4;
+            POINTER_DOWN = 5;
+            POINTER_UP = 6;
+        }
+
+        message Pointer {
+            optional float x = 1;
+            optional float y = 2;
+            optional float size = 3;
+            optional float pressure = 4;
+            optional int32 id = 5;
+            optional float removed_length = 6;
+            optional BoundingBox removed_boundingBox = 7;
+        }
+
+        optional uint64 timeOffsetNanos = 1;
+        optional Action action = 2;
+        optional int32 actionIndex = 3;
+        repeated Pointer pointers = 4;
+        optional bool removed_redacted = 5;
+        optional BoundingBox removed_boundingBox = 6;
+    }
+
+    message SensorEvent {
+        enum Type {
+            ACCELEROMETER = 1;
+            GYROSCOPE = 4;
+            LIGHT = 5;
+            PROXIMITY = 8;
+            ROTATION_VECTOR = 11;
+        }
+
+        optional Type type = 1;
+        optional uint64 timeOffsetNanos = 2;
+        repeated float values = 3;
+        optional uint64 timestamp = 4;
+    }
+
+    message PhoneEvent {
+        enum Type {
+            ON_SCREEN_ON = 0;
+            ON_SCREEN_ON_FROM_TOUCH = 1;
+            ON_SCREEN_OFF = 2;
+            ON_SUCCESSFUL_UNLOCK = 3;
+            ON_BOUNCER_SHOWN = 4;
+            ON_BOUNCER_HIDDEN = 5;
+            ON_QS_DOWN = 6;
+            SET_QS_EXPANDED_TRUE = 7;
+            SET_QS_EXPANDED_FALSE = 8;
+            ON_TRACKING_STARTED = 9;
+            ON_TRACKING_STOPPED = 10;
+            ON_NOTIFICATION_ACTIVE = 11;
+            ON_NOTIFICATION_INACTIVE = 12;
+            ON_NOTIFICATION_DOUBLE_TAP = 13;
+            SET_NOTIFICATION_EXPANDED = 14;
+            RESET_NOTIFICATION_EXPANDED = 15;
+            ON_NOTIFICATION_START_DRAGGING_DOWN = 16;
+            ON_NOTIFICATION_STOP_DRAGGING_DOWN = 17;
+            ON_NOTIFICATION_DISMISSED = 18;
+            ON_NOTIFICATION_START_DISMISSING = 19;
+            ON_NOTIFICATION_STOP_DISMISSING = 20;
+            ON_RIGHT_AFFORDANCE_SWIPING_STARTED = 21;
+            ON_LEFT_AFFORDANCE_SWIPING_STARTED = 22;
+            ON_AFFORDANCE_SWIPING_ABORTED = 23;
+            ON_CAMERA_ON = 24;
+            ON_LEFT_AFFORDANCE_ON = 25;
+            ON_UNLOCK_HINT_STARTED = 26;
+            ON_CAMERA_HINT_STARTED = 27;
+            ON_LEFT_AFFORDANCE_HINT_STARTED = 28;
+        }
+
+        optional Type type = 1;
+        optional uint64 timeOffsetNanos = 2;
+    }
+
+    enum Result {
+        FAILURE = 0;
+        SUCCESS = 1;
+        UNKNOWN = 2;
+    }
+
+    enum Type {
+        RESERVED_1 = 0;
+        RESERVED_2 = 1;
+        RANDOM_WAKEUP = 2;
+        REAL = 3;
+    }
+
+    optional uint64 startTimestampMillis = 1;
+    optional uint64 durationMillis = 2;
+    optional string build = 3;
+    optional Result result = 4;
+    repeated TouchEvent touchEvents = 5;
+    repeated SensorEvent sensorEvents = 6;
+
+    optional int32 touchAreaWidth = 9;
+    optional int32 touchAreaHeight = 10;
+    optional Type type = 11;
+    repeated PhoneEvent phoneEvents = 12;
+}
\ No newline at end of file
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 82c1aa8..e5bf768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -47,6 +47,7 @@
 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;
@@ -231,6 +232,7 @@
     private boolean mForceNoOverlappingRendering;
     private NotificationOverflowContainer mOverflowContainer;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
+    private LockedPhoneAnalytics mLockedPhoneAnalytics;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -264,6 +266,7 @@
             mDebugPaint.setStrokeWidth(2);
             mDebugPaint.setStyle(Paint.Style.STROKE);
         }
+        mLockedPhoneAnalytics = LockedPhoneAnalytics.getInstance(context);
     }
 
     @Override
@@ -595,6 +598,12 @@
             veto.performClick();
         }
         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
+
+        mLockedPhoneAnalytics.onNotificationDismissed();
+        if (mLockedPhoneAnalytics.shouldEnforceBouncer()) {
+            mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+                    false /* dismissShade */, true /* afterKeyguardGone */);
+        }
     }
 
     @Override
@@ -622,6 +631,7 @@
     }
 
     public void onBeginDrag(View v) {
+        mLockedPhoneAnalytics.onNotificatonStartDismissing();
         setSwipingInProgress(true);
         mAmbientState.onBeginDrag(v);
         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
@@ -648,6 +658,7 @@
     }
 
     public void onDragCancelled(View v) {
+        mLockedPhoneAnalytics.onNotificatonStopDismissing();
         setSwipingInProgress(false);
     }
 
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 6a7201c..d6d17cb 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -17,9 +17,14 @@
 
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
+
 LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-java-files-under, ../src) \
+    $(call all-proto-files-under, ../src) \
     src/com/android/systemui/EventLogTags.logtags
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \