diff options
author | Adrian Roos <roosa@google.com> | 2014-01-31 16:00:39 -0800 |
---|---|---|
committer | Adrian Roos <roosa@google.com> | 2014-03-03 17:09:27 +0100 |
commit | 58b58fe68b5b675672b8c4ad84e720365013d412 (patch) | |
tree | 72665f84b1ec92434c30036a52b543cbb7f8738d | |
parent | 1cdd7dda61b30358c843e534394c32f24bc5271f (diff) | |
download | frameworks_base-58b58fe68b5b675672b8c4ad84e720365013d412.zip frameworks_base-58b58fe68b5b675672b8c4ad84e720365013d412.tar.gz frameworks_base-58b58fe68b5b675672b8c4ad84e720365013d412.tar.bz2 |
Add session analytics to Keyguard.
On eng and userdebug builds, adds the possibility to track touch and sensor events on the Keyguard.
Change-Id: I9ff9fe5545cb9b7e6833a6af0b5a97a6c204dbd2
7 files changed, 775 insertions, 6 deletions
diff --git a/packages/Keyguard/Android.mk b/packages/Keyguard/Android.mk index 1f2b5fb..5b08674 100644 --- a/packages/Keyguard/Android.mk +++ b/packages/Keyguard/Android.mk @@ -16,7 +16,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) +LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) \ + $(call all-proto-files-under,src) LOCAL_PACKAGE_NAME := Keyguard @@ -26,6 +27,9 @@ LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags +LOCAL_PROTOC_OPTIMIZE_TYPE := nano +LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors + include $(BUILD_PACKAGE) #include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java index 40087cd..8738288 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java @@ -21,11 +21,11 @@ import android.graphics.drawable.BitmapDrawable; import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.analytics.KeyguardAnalytics; import org.xmlpull.v1.XmlPullParser; import android.app.ActivityManager; -import android.appwidget.AppWidgetManager; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; @@ -76,6 +76,7 @@ public class KeyguardViewManager { private final Context mContext; private final ViewManager mViewManager; private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; + private final KeyguardAnalytics.Callback mAnalyticsCallback; private WindowManager.LayoutParams mWindowLayoutParams; private boolean mNeedsInput = false; @@ -107,11 +108,12 @@ public class KeyguardViewManager { */ public KeyguardViewManager(Context context, ViewManager viewManager, KeyguardViewMediator.ViewMediatorCallback callback, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, KeyguardAnalytics.Callback analyticsCallback) { mContext = context; mViewManager = viewManager; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; + mAnalyticsCallback = analyticsCallback; } /** @@ -120,6 +122,9 @@ public class KeyguardViewManager { */ public synchronized void show(Bundle options) { if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); + if (mAnalyticsCallback != null) { + mAnalyticsCallback.onShow(); + } boolean enableScreenRotation = shouldEnableScreenRotation(); @@ -262,6 +267,15 @@ public class KeyguardViewManager { } return super.dispatchKeyEvent(event); } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean result = false; + if (mAnalyticsCallback != null) { + result = mAnalyticsCallback.onTouchEvent(ev, getWidth(), getHeight()) || result; + } + return super.dispatchTouchEvent(ev) || result; + } } SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>(); @@ -473,6 +487,9 @@ public class KeyguardViewManager { Slog.w(TAG, "Exception calling onShown():", e); } } + if (mAnalyticsCallback != null) { + mAnalyticsCallback.onScreenOn(); + } } public synchronized void verifyUnlock() { @@ -487,6 +504,10 @@ public class KeyguardViewManager { public synchronized void hide() { if (DEBUG) Log.d(TAG, "hide()"); + if (mAnalyticsCallback != null) { + mAnalyticsCallback.onHide(); + } + if (mKeyguardHost != null) { mKeyguardHost.setVisibility(View.GONE); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java index 151177e..31e806c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java @@ -19,6 +19,7 @@ package com.android.keyguard; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardShowCallback; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapter; import android.app.Activity; import android.app.ActivityManagerNative; @@ -33,6 +34,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.media.SoundPool; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -54,6 +56,10 @@ import android.view.WindowManagerPolicy; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.analytics.Session; +import com.android.keyguard.analytics.KeyguardAnalytics; + +import java.io.File; /** @@ -100,6 +106,7 @@ import com.android.internal.widget.LockPatternUtils; public class KeyguardViewMediator { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; final static boolean DEBUG = false; + private static final boolean ENABLE_ANALYTICS = Build.IS_DEBUGGABLE; private final static boolean DBG_WAKE = false; private final static String TAG = "KeyguardViewMediator"; @@ -161,6 +168,11 @@ public class KeyguardViewMediator { */ private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false; + /** + * Secure setting whether analytics are collected on the keyguard. + */ + private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics"; + /** The stream type that the lock sounds are tied to. */ private int mMasterStreamType; @@ -194,6 +206,8 @@ public class KeyguardViewMediator { private KeyguardViewManager mKeyguardViewManager; + private final KeyguardAnalytics mKeyguardAnalytics; + // these are protected by synchronized (this) /** @@ -528,11 +542,24 @@ public class KeyguardViewMediator { && !mLockPatternUtils.isLockScreenDisabled(); WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + final ContentResolver cr = mContext.getContentResolver(); - mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, - mLockPatternUtils); + if (ENABLE_ANALYTICS && !LockPatternUtils.isSafeModeEnabled() && + Settings.Secure.getInt(cr, KEYGUARD_ANALYTICS_SETTING, 0) == 1) { + mKeyguardAnalytics = new KeyguardAnalytics(context, new SessionTypeAdapter() { - final ContentResolver cr = mContext.getContentResolver(); + @Override + public int getSessionType() { + return mLockPatternUtils.isSecure() ? Session.TYPE_KEYGUARD_SECURE + : Session.TYPE_KEYGUARD_INSECURE; + } + }, new File(mContext.getCacheDir(), "keyguard_analytics.bin")); + } else { + mKeyguardAnalytics = null; + } + mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, + mLockPatternUtils, + mKeyguardAnalytics != null ? mKeyguardAnalytics.getCallback() : null); mScreenOn = mPM.isScreenOn(); @@ -631,6 +658,9 @@ public class KeyguardViewMediator { } else { doKeyguardLocked(null); } + if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { + mKeyguardAnalytics.getCallback().onScreenOff(); + } } KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); } @@ -869,6 +899,9 @@ public class KeyguardViewMediator { updateActivityLockScreenState(); adjustStatusBarLocked(); } + if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { + mKeyguardAnalytics.getCallback().onSetHidden(isHidden); + } } } diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java b/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java new file mode 100644 index 0000000..48ad16b --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2014 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.keyguard.analytics; + +import com.google.protobuf.nano.CodedOutputByteBufferNano; +import com.google.protobuf.nano.MessageNano; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.AsyncTask; +import android.util.Log; +import android.view.MotionEvent; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Tracks sessions, touch and sensor events in Keyguard. + * + * A session starts when the user is presented with the Keyguard and ends when the Keyguard is no + * longer visible to the user. + */ +public class KeyguardAnalytics implements SensorEventListener { + + private static final boolean DEBUG = false; + private static final String TAG = "KeyguardAnalytics"; + private static final long TIMEOUT_MILLIS = 11000; // 11 seconds. + + private static final String ANALYTICS_FILE = "/sdcard/keyguard_analytics.bin"; + + private static final int[] SENSORS = new int[] { + Sensor.TYPE_ACCELEROMETER, + Sensor.TYPE_GYROSCOPE, + Sensor.TYPE_PROXIMITY, + Sensor.TYPE_LIGHT, + Sensor.TYPE_ROTATION_VECTOR, + }; + + private Session mCurrentSession = null; + // Err on the side of caution, so logging is not started after a crash even tough the screen + // is off. + private boolean mScreenOn = false; + private boolean mHidden = false; + + private final SensorManager mSensorManager; + private final SessionTypeAdapter mSessionTypeAdapter; + private final File mAnalyticsFile; + + public KeyguardAnalytics(Context context, SessionTypeAdapter sessionTypeAdapter, + File analyticsFile) { + mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + mSessionTypeAdapter = sessionTypeAdapter; + mAnalyticsFile = analyticsFile; + } + + public Callback getCallback() { + return mCallback; + } + + public interface Callback { + public void onShow(); + public void onHide(); + public void onScreenOn(); + public void onScreenOff(); + public boolean onTouchEvent(MotionEvent ev, int width, int height); + public void onSetHidden(boolean hidden); + } + + public interface SessionTypeAdapter { + public int getSessionType(); + } + + private void sessionEntrypoint() { + if (mCurrentSession == null && mScreenOn && !mHidden) { + onSessionStart(); + } + } + + private void sessionExitpoint(int result) { + if (mCurrentSession != null) { + onSessionEnd(result); + } + } + + private void onSessionStart() { + int type = mSessionTypeAdapter.getSessionType(); + mCurrentSession = new Session(System.currentTimeMillis(), System.nanoTime(), type); + if (type == Session.TYPE_KEYGUARD_SECURE) { + mCurrentSession.setRedactTouchEvents(); + } + for (int sensorType : SENSORS) { + Sensor s = mSensorManager.getDefaultSensor(sensorType); + if (s != null) { + mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME); + } + } + if (DEBUG) { + Log.d(TAG, "onSessionStart()"); + } + } + + private void onSessionEnd(int result) { + if (DEBUG) { + Log.d(TAG, String.format("onSessionEnd(success=%d)", result)); + } + mSensorManager.unregisterListener(this); + + Session session = mCurrentSession; + mCurrentSession = null; + + session.end(System.currentTimeMillis(), result); + queueSession(session); + } + + private void queueSession(final Session currentSession) { + if (DEBUG) { + Log.i(TAG, "Saving session."); + } + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + try { + byte[] b = writeDelimitedProto(currentSession.toProto()); + OutputStream os = new FileOutputStream(mAnalyticsFile, true /* append */); + if (DEBUG) { + Log.d(TAG, String.format("Serialized size: %d kB.", b.length / 1024)); + } + try { + os.write(b); + os.flush(); + } finally { + try { + os.close(); + } catch (IOException e) { + Log.e(TAG, "Exception while closing file", e); + } + } + } catch (IOException e) { + Log.e(TAG, "Exception while writing file", e); + } + return null; + } + + private byte[] writeDelimitedProto(MessageNano proto) + throws IOException { + byte[] result = new byte[CodedOutputByteBufferNano.computeMessageSizeNoTag(proto)]; + CodedOutputByteBufferNano ob = CodedOutputByteBufferNano.newInstance(result); + ob.writeMessageNoTag(proto); + ob.checkNoSpaceLeft(); + return result; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + } + + @Override + public synchronized void onSensorChanged(SensorEvent event) { + if (false) { + Log.v(TAG, String.format( + "onSensorChanged(name=%s, values[0]=%f)", + event.sensor.getName(), event.values[0])); + } + if (mCurrentSession != null) { + mCurrentSession.addSensorEvent(event, System.nanoTime()); + enforceTimeout(); + } + } + + private void enforceTimeout() { + if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis() + > TIMEOUT_MILLIS) { + onSessionEnd(Session.RESULT_UNKNOWN); + if (DEBUG) { + Log.i(TAG, "Analytics timed out."); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + + private final Callback mCallback = new Callback() { + @Override + public void onShow() { + if (DEBUG) { + Log.d(TAG, "onShow()"); + } + synchronized (KeyguardAnalytics.this) { + sessionEntrypoint(); + } + } + + @Override + public void onHide() { + if (DEBUG) { + Log.d(TAG, "onHide()"); + } + synchronized (KeyguardAnalytics.this) { + sessionExitpoint(Session.RESULT_SUCCESS); + } + } + + @Override + public void onScreenOn() { + if (DEBUG) { + Log.d(TAG, "onScreenOn()"); + } + synchronized (KeyguardAnalytics.this) { + mScreenOn = true; + sessionEntrypoint(); + } + } + + @Override + public void onScreenOff() { + if (DEBUG) { + Log.d(TAG, "onScreenOff()"); + } + synchronized (KeyguardAnalytics.this) { + mScreenOn = false; + sessionExitpoint(Session.RESULT_FAILURE); + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev, int width, int height) { + if (DEBUG) { + Log.v(TAG, "onTouchEvent(ev.action=" + + MotionEvent.actionToString(ev.getAction()) + ")"); + } + synchronized (KeyguardAnalytics.this) { + if (mCurrentSession != null) { + mCurrentSession.addMotionEvent(ev); + mCurrentSession.setTouchArea(width, height); + enforceTimeout(); + } + } + return true; + } + + @Override + public void onSetHidden(boolean hidden) { + synchronized (KeyguardAnalytics.this) { + if (hidden != mHidden) { + if (DEBUG) { + Log.d(TAG, "onSetHidden(" + hidden + ")"); + } + mHidden = hidden; + if (hidden) { + // Could have gone to camera on purpose / by falsing or an app could have + // launched on top of the lockscreen. + sessionExitpoint(Session.RESULT_UNKNOWN); + } else { + sessionEntrypoint(); + } + } + } + } + }; + +} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java b/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java new file mode 100644 index 0000000..e68f751 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 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.keyguard.analytics; + +import android.graphics.RectF; +import android.util.FloatMath; +import android.util.SparseArray; +import android.view.MotionEvent; + +import java.util.HashMap; +import java.util.Map; + +import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent.BoundingBox; + +/** + * Takes motion events and tracks the length and bounding box of each pointer gesture as well as + * the bounding box of the whole gesture. + */ +public class PointerTracker { + private SparseArray<Pointer> mPointerInfoMap = new SparseArray<Pointer>(); + private RectF mTotalBoundingBox = new RectF(); + + public void addMotionEvent(MotionEvent ev) { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + float x = ev.getX(); + float y = ev.getY(); + mTotalBoundingBox.set(x, y, x, y); + } + for (int i = 0; i < ev.getPointerCount(); i++) { + int id = ev.getPointerId(i); + Pointer pointer = getPointer(id); + float x = ev.getX(i); + float y = ev.getY(i); + boolean down = ev.getActionMasked() == MotionEvent.ACTION_DOWN + || (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN + && ev.getActionIndex() == i); + pointer.addPoint(x, y, down); + mTotalBoundingBox.union(x, y); + } + } + + public float getPointerLength(int id) { + return getPointer(id).length; + } + + public BoundingBox getBoundingBox() { + return boundingBoxFromRect(mTotalBoundingBox); + } + + public BoundingBox getPointerBoundingBox(int id) { + return boundingBoxFromRect(getPointer(id).boundingBox); + } + + private BoundingBox boundingBoxFromRect(RectF f) { + BoundingBox bb = new BoundingBox(); + bb.setHeight(f.height()); + bb.setWidth(f.width()); + return bb; + } + + private Pointer getPointer(int id) { + Pointer p = mPointerInfoMap.get(id); + if (p == null) { + p = new Pointer(); + mPointerInfoMap.put(id, p); + } + return p; + } + + private static class Pointer { + public float length; + public final RectF boundingBox = new RectF(); + + private float mLastX; + private float mLastY; + + public void addPoint(float x, float y, boolean down) { + float deltaX; + float deltaY; + if (down) { + boundingBox.set(x, y, x, y); + length = 0f; + deltaX = 0; + deltaY = 0; + } else { + deltaX = x - mLastX; + deltaY = y - mLastY; + } + mLastX = x; + mLastY = y; + length += FloatMath.sqrt(deltaX * deltaX + deltaY * deltaY); + boundingBox.union(x, y); + } + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/Session.java b/packages/Keyguard/src/com/android/keyguard/analytics/Session.java new file mode 100644 index 0000000..05f9165 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/analytics/Session.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2014 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.keyguard.analytics; + +import android.os.Build; +import android.util.Slog; +import android.view.MotionEvent; + +import java.util.ArrayList; + +import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.SensorEvent; +import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent; + +/** + * Records data about one keyguard session. + * + * The recorded data contains start and end of the session, whether it unlocked the device + * successfully, sensor data and touch data. + * + * If the keyguard is secure, the recorded touch data will correlate or contain the user pattern or + * PIN. If this is not desired, the touch coordinates can be redacted before serialization. + */ +public class Session { + + private static final String TAG = "KeyguardAnalytics"; + private static final boolean DEBUG = false; + + /** + * The user has failed to unlock the device in this session. + */ + public static final int RESULT_FAILURE = KeyguardAnalyticsProtos.Session.FAILURE; + /** + * The user has succeeded in unlocking the device in this session. + */ + public static final int RESULT_SUCCESS = KeyguardAnalyticsProtos.Session.SUCCESS; + + /** + * It is unknown how the session with the keyguard ended. + */ + public static final int RESULT_UNKNOWN = KeyguardAnalyticsProtos.Session.UNKNOWN; + + /** + * This session took place on an insecure keyguard. + */ + public static final int TYPE_KEYGUARD_INSECURE + = KeyguardAnalyticsProtos.Session.KEYGUARD_INSECURE; + + /** + * This session took place on an secure keyguard. + */ + public static final int TYPE_KEYGUARD_SECURE + = KeyguardAnalyticsProtos.Session.KEYGUARD_SECURE; + + /** + * This session took place during a fake wake up of the device. + */ + public static final int TYPE_RANDOM_WAKEUP = KeyguardAnalyticsProtos.Session.RANDOM_WAKEUP; + + + private final PointerTracker mPointerTracker = new PointerTracker(); + + private final long mStartTimestampMillis; + private final long mStartSystemTimeNanos; + private final int mType; + + private boolean mRedactTouchEvents; + private ArrayList<TouchEvent> mMotionEvents = new ArrayList<TouchEvent>(200); + private ArrayList<SensorEvent> mSensorEvents = new ArrayList<SensorEvent>(600); + private int mTouchAreaHeight; + private int mTouchAreaWidth; + + private long mEndTimestampMillis; + private int mResult; + private boolean mEnded; + + public Session(long startTimestampMillis, long startSystemTimeNanos, int type) { + mStartTimestampMillis = startTimestampMillis; + mStartSystemTimeNanos = startSystemTimeNanos; + mType = type; + } + + public void end(long endTimestampMillis, int result) { + mEnded = true; + mEndTimestampMillis = endTimestampMillis; + mResult = result; + } + + public void addMotionEvent(MotionEvent motionEvent) { + if (mEnded) { + return; + } + mPointerTracker.addMotionEvent(motionEvent); + mMotionEvents.add(protoFromMotionEvent(motionEvent)); + } + + public void addSensorEvent(android.hardware.SensorEvent eventOrig, long systemTimeNanos) { + if (mEnded) { + return; + } + SensorEvent event = protoFromSensorEvent(eventOrig, systemTimeNanos); + mSensorEvents.add(event); + if (DEBUG) { + Slog.v(TAG, String.format("addSensorEvent(name=%s, values[0]=%f", + event.getType(), event.values[0])); + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Session{"); + sb.append("mType=").append(mType); + sb.append(", mStartTimestampMillis=").append(mStartTimestampMillis); + sb.append(", mStartSystemTimeNanos=").append(mStartSystemTimeNanos); + sb.append(", mEndTimestampMillis=").append(mEndTimestampMillis); + sb.append(", mResult=").append(mResult); + sb.append(", mRedactTouchEvents=").append(mRedactTouchEvents); + 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('}'); + return sb.toString(); + } + + public KeyguardAnalyticsProtos.Session toProto() { + KeyguardAnalyticsProtos.Session proto = new KeyguardAnalyticsProtos.Session(); + proto.setStartTimestampMillis(mStartTimestampMillis); + proto.setDurationMillis(mEndTimestampMillis - mStartTimestampMillis); + proto.setBuild(Build.FINGERPRINT); + proto.setResult(mResult); + proto.sensorEvents = mSensorEvents.toArray(proto.sensorEvents); + proto.touchEvents = mMotionEvents.toArray(proto.touchEvents); + proto.setTouchAreaWidth(mTouchAreaWidth); + proto.setTouchAreaHeight(mTouchAreaHeight); + proto.setType(mType); + if (mRedactTouchEvents) { + redactTouchEvents(proto.touchEvents); + } + return proto; + } + + private void redactTouchEvents(TouchEvent[] touchEvents) { + for (int i = 0; i < touchEvents.length; i++) { + TouchEvent t = touchEvents[i]; + for (int j = 0; j < t.pointers.length; j++) { + TouchEvent.Pointer p = t.pointers[j]; + p.clearX(); + p.clearY(); + } + t.setRedacted(true); + } + } + + private SensorEvent protoFromSensorEvent(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 protoFromMotionEvent(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; + if ((ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP && ev.getActionIndex() == i) + || ev.getActionMasked() == MotionEvent.ACTION_UP) { + p.boundingBox = mPointerTracker.getPointerBoundingBox(p.getId()); + p.setLength(mPointerTracker.getPointerLength(p.getId())); + } + } + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + proto.boundingBox = mPointerTracker.getBoundingBox(); + } + return proto; + } + + /** + * Discards the x / y coordinates of the touch events on serialization. Retained are the + * size of the individual and overall bounding boxes and the length of each pointer's gesture. + */ + public void setRedactTouchEvents() { + mRedactTouchEvents = true; + } + + public void setTouchArea(int width, int height) { + mTouchAreaWidth = width; + mTouchAreaHeight = height; + } + + public long getStartTimestampMillis() { + return mStartTimestampMillis; + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto b/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto new file mode 100644 index 0000000..68b1590 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 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 keyguard; + +option java_package = "com.android.keyguard.analytics"; +option java_outer_classname = "KeyguardAnalyticsProtos"; + +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 length = 6; + // Bounding box of the pointer. Only set on UP or POINTER_UP event of this pointer. + optional BoundingBox boundingBox = 7; + } + + optional uint64 timeOffsetNanos = 1; + optional Action action = 2; + optional int32 actionIndex = 3; + repeated Pointer pointers = 4; + /* If true, the the x / y coordinates of the touch events were redacted. Retained are the + size of the individual and overall bounding boxes and the length of each pointer's + gesture. */ + optional bool redacted = 5; + // Bounding box of the whole gesture. Only set on UP event. + optional BoundingBox 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; + } + + enum Result { + FAILURE = 0; + SUCCESS = 1; + UNKNOWN = 2; + } + + enum Type { + KEYGUARD_INSECURE = 0; + KEYGUARD_SECURE = 1; + RANDOM_WAKEUP = 2; + } + + 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; +} |