Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bba9a212 authored by Adrian Roos's avatar Adrian Roos
Browse files

Remove now defunct touch analysis facility

Bug: 15381470
Change-Id: I2ab252e4c0122d79c9a78b3df643bfd46a105cf0
parent 05259114
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -16,8 +16,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) \
                   $(call all-proto-files-under,src)
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files)

LOCAL_MODULE := Keyguard

@@ -27,9 +26,6 @@ LOCAL_PRIVILEGED_MODULE := true

LOCAL_PROGUARD_FLAG_FILES := proguard.flags

LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors

LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res

include $(BUILD_STATIC_JAVA_LIBRARY)
+0 −278
Original line number Diff line number Diff line
/*
 * 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 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 onSetOccluded(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 onSetOccluded(boolean hidden) {
            synchronized (KeyguardAnalytics.this) {
                if (hidden != mHidden) {
                    if (DEBUG) {
                        Log.d(TAG, "onSetOccluded(" + 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();
                    }
                }
            }
        }
    };

}
+0 −109
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+0 −220
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+0 −102
Original line number Diff line number Diff line
/*
 * 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;
}
Loading