/* * 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 mMotionEvents = new ArrayList(200); private ArrayList mSensorEvents = new ArrayList(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; } }