Loading packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java→packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java +39 −84 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ 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; Loading @@ -30,8 +29,6 @@ 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; Loading @@ -47,94 +44,70 @@ import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.P * A session starts when the screen is turned on. * A session ends when the screen is turned off or user unlocks the phone. */ public class LockedPhoneAnalytics implements SensorEventListener { private static final String TAG = "LockedPhoneAnalytics"; private static final String ANALYTICS_ENABLE = "locked_phone_analytics_enable"; private static final String ENFORCE_BOUNCER = "locked_phone_analytics_enforce_bouncer"; private static final String COLLECT_BAD_TOCUHES = "locked_phone_analytics_collect_bad_touches"; public class DataCollector implements SensorEventListener { private static final String TAG = "DataCollector"; private static final String COLLECTOR_ENABLE = "data_collector_enable"; private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches"; private static final long TIMEOUT_MILLIS = 11000; // 11 seconds. public static final boolean DEBUG = false; private static final int[] SENSORS = new int[] { Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE, Sensor.TYPE_PROXIMITY, Sensor.TYPE_LIGHT, Sensor.TYPE_ROTATION_VECTOR, }; private final Handler mHandler = new Handler(); 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 mEnableCollector = 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 DataCollector sInstance = null; private static LockedPhoneAnalytics sInstance = null; protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { updateConfiguration(); } }; private LockedPhoneAnalytics(Context context) { mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); private DataCollector(Context context) { mContext = context; mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(ANALYTICS_ENABLE), false, Settings.Secure.getUriFor(COLLECTOR_ENABLE), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(ENFORCE_BOUNCER), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(COLLECT_BAD_TOCUHES), false, Settings.Secure.getUriFor(COLLECT_BAD_TOUCHES), false, mSettingsObserver, UserHandle.USER_ALL); updateConfiguration(); } public static LockedPhoneAnalytics getInstance(Context context) { public static DataCollector getInstance(Context context) { if (sInstance == null) { sInstance = new LockedPhoneAnalytics(context); sInstance = new DataCollector(context); } return sInstance; } private void updateConfiguration() { mEnableAnalytics = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt( mContext.getContentResolver(), ANALYTICS_ENABLE, 0); mEnforceBouncer = mEnableAnalytics && 0 != Settings.Secure.getInt( mEnableCollector = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt( mContext.getContentResolver(), ENFORCE_BOUNCER, 0); mCollectBadTouches = mEnableAnalytics && 0 != Settings.Secure.getInt( COLLECTOR_ENABLE, 0); mCollectBadTouches = mEnableCollector && 0 != Settings.Secure.getInt( mContext.getContentResolver(), COLLECT_BAD_TOCUHES, 0); COLLECT_BAD_TOUCHES, 0); } private boolean sessionEntrypoint() { if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) && mEnableAnalytics && mCurrentSession == null) { if (mEnableCollector && mCurrentSession == null) { onSessionStart(); return true; } Loading @@ -142,22 +115,15 @@ public class LockedPhoneAnalytics implements SensorEventListener { } private void sessionExitpoint(int result) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { onSessionEnd(result); } } private void onSessionStart() { mBouncerOn = false; mCornerSwiping = false; mTrackingStarted = false; mCurrentSession = new SensorLoggerSession(System.currentTimeMillis(), System.nanoTime()); for (int sensorType : SENSORS) { Sensor s = mSensorManager.getDefaultSensor(sensorType); if (s != null) { mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME); } } } private void onSessionEnd(int result) { Loading Loading @@ -196,10 +162,9 @@ public class LockedPhoneAnalytics implements SensorEventListener { }); } @Override public synchronized void onSensorChanged(SensorEvent event) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { mCurrentSession.addSensorEvent(event, System.nanoTime()); enforceTimeout(); } Loading @@ -221,18 +186,14 @@ public class LockedPhoneAnalytics implements SensorEventListener { public void onAccuracyChanged(Sensor sensor, int accuracy) { } public boolean shouldEnforceBouncer() { return mEnforceBouncer; public boolean isEnabled() { return mEnableCollector; } public void setStatusBarState(int state) { mState = state; } public void onScreenOn() { public void onScreenTurningOn() { if (sessionEntrypoint()) { if (DEBUG) { Log.d(TAG, "onScreenOn"); Log.d(TAG, "onScreenTurningOn"); } addEvent(PhoneEvent.ON_SCREEN_ON); } Loading Loading @@ -264,24 +225,18 @@ public class LockedPhoneAnalytics implements SensorEventListener { } 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) { Loading Loading @@ -433,20 +388,20 @@ public class LockedPhoneAnalytics implements SensorEventListener { addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_HINT_STARTED); } public void onTouchEvent(MotionEvent ev, int width, int height) { if (!mBouncerOn && mCurrentSession != null) { public void onTouchEvent(MotionEvent event, int width, int height) { if (mCurrentSession != null) { if (DEBUG) { Log.v(TAG, "onTouchEvent(ev.action=" + MotionEvent.actionToString(ev.getAction()) + ")"); + MotionEvent.actionToString(event.getAction()) + ")"); } mCurrentSession.addMotionEvent(ev); mCurrentSession.addMotionEvent(event); mCurrentSession.setTouchArea(width, height); enforceTimeout(); } } private void addEvent(int eventType) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { mCurrentSession.addPhoneEvent(eventType, System.nanoTime()); } } Loading packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ public class SensorLoggerSession { mResult = result; mEndTimestampMillis = endTimestampMillis; if (LockedPhoneAnalytics.DEBUG) { if (DataCollector.DEBUG) { Log.d(TAG, "Ending session result=" + result + " it lasted for " + (float) (mEndTimestampMillis - mStartTimestampMillis) / 1000f + "s"); } Loading packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.hardware.SensorEvent; import android.view.MotionEvent; import java.lang.Math; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * A classifier which calculates the variance of differences between successive angles in a stroke. * For each stroke it keeps its last three points. If some successive points are the same, it ignores * the repetitions. If a new point is added, the classifier calculates the angle between the last * three points. After that it calculates the difference between this angle and the previously * calculated angle. The return value of the classifier is the variance of the differences * from a stroke. If there are multiple strokes created at once, the classifier sums up the * variances of all the strokes. Also the value is multiplied by HISTORY_FACTOR after each * INTERVAL milliseconds. */ public class AnglesVarianceClassifier extends Classifier { private final float INTERVAL = 10.0f; private final float CLEAR_HISTORY = 500f; private final float HISTORY_FACTOR = 0.9f; private HashMap<Stroke, Data> mStrokeMap = new HashMap<>(); private float mValue; private long mLastUpdate; public AnglesVarianceClassifier(ClassifierData classifierData) { mClassifierData = classifierData; mValue = 0.0f; mLastUpdate = System.currentTimeMillis(); } @Override public void onTouchEvent(MotionEvent event) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mStrokeMap.clear(); } for (int i = 0; i < event.getPointerCount(); i++) { Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); if (mStrokeMap.get(stroke) == null) { mStrokeMap.put(stroke, new Data()); } mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1)); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { decayValue(); mValue += mStrokeMap.get(stroke).getAnglesVariance(); } } } /** * Decreases mValue through time */ private void decayValue() { long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis - mLastUpdate > CLEAR_HISTORY) { mValue = 0.0f; } else { mValue *= Math.pow(HISTORY_FACTOR, (float) (currentTimeMillis - mLastUpdate) / INTERVAL); } mLastUpdate = currentTimeMillis; } @Override public void onSensorChanged(SensorEvent event) { } @Override public float getFalseTouchEvaluation(int type) { decayValue(); float currentValue = 0.0f; for (Data data: mStrokeMap.values()) { currentValue += data.getAnglesVariance(); } return (float) (mValue + currentValue); } private class Data { private List<Point> mLastThreePoints = new ArrayList<>(); private float mPreviousAngle; private float mSumSquares; private float mSum; private float mCount; public Data() { mPreviousAngle = (float) Math.PI; mSumSquares = 0.0f; mSum = 0.0f; mCount = 1.0f; } public void addPoint(Point point) { // Checking if the added point is different than the previously added point // Repetitions are being ignored so that proper angles are calculated. if (mLastThreePoints.isEmpty() || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) { mLastThreePoints.add(point); if (mLastThreePoints.size() == 4) { mLastThreePoints.remove(0); float angle = getAngle(mLastThreePoints.get(0), mLastThreePoints.get(1), mLastThreePoints.get(2)); float difference = angle - mPreviousAngle; mSum += difference; mSumSquares += difference * difference; mCount += 1.0; mPreviousAngle = angle; } } } private float getAngle(Point a, Point b, Point c) { float dist1 = a.dist(b); float dist2 = b.dist(c); float crossProduct = b.crossProduct(a, c); float dotProduct = b.dotProduct(a, c); float cos = Math.min(1.0f, Math.max(-1.0f, dotProduct / dist1 / dist2)); float angle = (float) Math.acos(cos); if (crossProduct < 0.0) { angle = 2.0f * (float) Math.PI - angle; } return angle; } public float getAnglesVariance() { return mSumSquares / mCount + (mSum / mCount) * (mSum / mCount); } } } No newline at end of file packages/SystemUI/src/com/android/systemui/classifier/Classifier.java 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.hardware.SensorEvent; import android.view.MotionEvent; /** * An interface for classifiers for touch and sensor events. */ public abstract class Classifier { public static final int QUICK_SETTINGS = 0; public static final int NOTIFICATION_DISMISS = 1; public static final int NOTIFICATION_DRAG_DOWN = 2; public static final int NOTIFICATION_DOUBLE_TAP = 3; public static final int UNLOCK = 4; public static final int LEFT_AFFORDANCE = 5; public static final int RIGHT_AFFORDANCE = 6; /** * Contains all the information about touch events from which the classifier can query */ protected ClassifierData mClassifierData; /** * Informs the classifier that a new touch event has occurred */ public void onTouchEvent(MotionEvent event) { } /** * Informs the classifier that a sensor change occurred */ public void onSensorChanged(SensorEvent event) { } /** * @param type the type of action for which this method is called * @return a nonnegative value which is used to determine whether this a false touch. The * bigger the value the greater the chance that this a false touch. */ public abstract float getFalseTouchEvaluation(int type); } packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.util.SparseArray; import android.view.MotionEvent; /** * Contains data which is used to classify interaction sequences on the lockscreen. It does, for * example, provide information on the current touch state. */ public class ClassifierData { private SparseArray<Stroke> mCurrentStrokes = new SparseArray<>(); public ClassifierData() { } public void update(MotionEvent event) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mCurrentStrokes.clear(); } for (int i = 0; i < event.getPointerCount(); i++) { int id = event.getPointerId(i); if (mCurrentStrokes.get(id) == null) { mCurrentStrokes.put(id, new Stroke(event.getEventTimeNano())); } mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i), event.getEventTimeNano()); } } public void cleanUp(MotionEvent event) { int action = event.getActionMasked(); for (int i = 0; i < event.getPointerCount(); i++) { int id = event.getPointerId(i); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { mCurrentStrokes.remove(id); } } } /** * @param id the id from MotionEvent * @return the Stroke assigned to the id */ public Stroke getStroke(int id) { return mCurrentStrokes.get(id); } } Loading
packages/SystemUI/src/com/android/systemui/analytics/LockedPhoneAnalytics.java→packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java +39 −84 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ 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; Loading @@ -30,8 +29,6 @@ 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; Loading @@ -47,94 +44,70 @@ import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.P * A session starts when the screen is turned on. * A session ends when the screen is turned off or user unlocks the phone. */ public class LockedPhoneAnalytics implements SensorEventListener { private static final String TAG = "LockedPhoneAnalytics"; private static final String ANALYTICS_ENABLE = "locked_phone_analytics_enable"; private static final String ENFORCE_BOUNCER = "locked_phone_analytics_enforce_bouncer"; private static final String COLLECT_BAD_TOCUHES = "locked_phone_analytics_collect_bad_touches"; public class DataCollector implements SensorEventListener { private static final String TAG = "DataCollector"; private static final String COLLECTOR_ENABLE = "data_collector_enable"; private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches"; private static final long TIMEOUT_MILLIS = 11000; // 11 seconds. public static final boolean DEBUG = false; private static final int[] SENSORS = new int[] { Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE, Sensor.TYPE_PROXIMITY, Sensor.TYPE_LIGHT, Sensor.TYPE_ROTATION_VECTOR, }; private final Handler mHandler = new Handler(); 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 mEnableCollector = 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 DataCollector sInstance = null; private static LockedPhoneAnalytics sInstance = null; protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { updateConfiguration(); } }; private LockedPhoneAnalytics(Context context) { mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); private DataCollector(Context context) { mContext = context; mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(ANALYTICS_ENABLE), false, Settings.Secure.getUriFor(COLLECTOR_ENABLE), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(ENFORCE_BOUNCER), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(COLLECT_BAD_TOCUHES), false, Settings.Secure.getUriFor(COLLECT_BAD_TOUCHES), false, mSettingsObserver, UserHandle.USER_ALL); updateConfiguration(); } public static LockedPhoneAnalytics getInstance(Context context) { public static DataCollector getInstance(Context context) { if (sInstance == null) { sInstance = new LockedPhoneAnalytics(context); sInstance = new DataCollector(context); } return sInstance; } private void updateConfiguration() { mEnableAnalytics = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt( mContext.getContentResolver(), ANALYTICS_ENABLE, 0); mEnforceBouncer = mEnableAnalytics && 0 != Settings.Secure.getInt( mEnableCollector = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt( mContext.getContentResolver(), ENFORCE_BOUNCER, 0); mCollectBadTouches = mEnableAnalytics && 0 != Settings.Secure.getInt( COLLECTOR_ENABLE, 0); mCollectBadTouches = mEnableCollector && 0 != Settings.Secure.getInt( mContext.getContentResolver(), COLLECT_BAD_TOCUHES, 0); COLLECT_BAD_TOUCHES, 0); } private boolean sessionEntrypoint() { if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) && mEnableAnalytics && mCurrentSession == null) { if (mEnableCollector && mCurrentSession == null) { onSessionStart(); return true; } Loading @@ -142,22 +115,15 @@ public class LockedPhoneAnalytics implements SensorEventListener { } private void sessionExitpoint(int result) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { onSessionEnd(result); } } private void onSessionStart() { mBouncerOn = false; mCornerSwiping = false; mTrackingStarted = false; mCurrentSession = new SensorLoggerSession(System.currentTimeMillis(), System.nanoTime()); for (int sensorType : SENSORS) { Sensor s = mSensorManager.getDefaultSensor(sensorType); if (s != null) { mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME); } } } private void onSessionEnd(int result) { Loading Loading @@ -196,10 +162,9 @@ public class LockedPhoneAnalytics implements SensorEventListener { }); } @Override public synchronized void onSensorChanged(SensorEvent event) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { mCurrentSession.addSensorEvent(event, System.nanoTime()); enforceTimeout(); } Loading @@ -221,18 +186,14 @@ public class LockedPhoneAnalytics implements SensorEventListener { public void onAccuracyChanged(Sensor sensor, int accuracy) { } public boolean shouldEnforceBouncer() { return mEnforceBouncer; public boolean isEnabled() { return mEnableCollector; } public void setStatusBarState(int state) { mState = state; } public void onScreenOn() { public void onScreenTurningOn() { if (sessionEntrypoint()) { if (DEBUG) { Log.d(TAG, "onScreenOn"); Log.d(TAG, "onScreenTurningOn"); } addEvent(PhoneEvent.ON_SCREEN_ON); } Loading Loading @@ -264,24 +225,18 @@ public class LockedPhoneAnalytics implements SensorEventListener { } 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) { Loading Loading @@ -433,20 +388,20 @@ public class LockedPhoneAnalytics implements SensorEventListener { addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_HINT_STARTED); } public void onTouchEvent(MotionEvent ev, int width, int height) { if (!mBouncerOn && mCurrentSession != null) { public void onTouchEvent(MotionEvent event, int width, int height) { if (mCurrentSession != null) { if (DEBUG) { Log.v(TAG, "onTouchEvent(ev.action=" + MotionEvent.actionToString(ev.getAction()) + ")"); + MotionEvent.actionToString(event.getAction()) + ")"); } mCurrentSession.addMotionEvent(ev); mCurrentSession.addMotionEvent(event); mCurrentSession.setTouchArea(width, height); enforceTimeout(); } } private void addEvent(int eventType) { if (mEnableAnalytics && mCurrentSession != null) { if (mEnableCollector && mCurrentSession != null) { mCurrentSession.addPhoneEvent(eventType, System.nanoTime()); } } Loading
packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ public class SensorLoggerSession { mResult = result; mEndTimestampMillis = endTimestampMillis; if (LockedPhoneAnalytics.DEBUG) { if (DataCollector.DEBUG) { Log.d(TAG, "Ending session result=" + result + " it lasted for " + (float) (mEndTimestampMillis - mStartTimestampMillis) / 1000f + "s"); } Loading
packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.hardware.SensorEvent; import android.view.MotionEvent; import java.lang.Math; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * A classifier which calculates the variance of differences between successive angles in a stroke. * For each stroke it keeps its last three points. If some successive points are the same, it ignores * the repetitions. If a new point is added, the classifier calculates the angle between the last * three points. After that it calculates the difference between this angle and the previously * calculated angle. The return value of the classifier is the variance of the differences * from a stroke. If there are multiple strokes created at once, the classifier sums up the * variances of all the strokes. Also the value is multiplied by HISTORY_FACTOR after each * INTERVAL milliseconds. */ public class AnglesVarianceClassifier extends Classifier { private final float INTERVAL = 10.0f; private final float CLEAR_HISTORY = 500f; private final float HISTORY_FACTOR = 0.9f; private HashMap<Stroke, Data> mStrokeMap = new HashMap<>(); private float mValue; private long mLastUpdate; public AnglesVarianceClassifier(ClassifierData classifierData) { mClassifierData = classifierData; mValue = 0.0f; mLastUpdate = System.currentTimeMillis(); } @Override public void onTouchEvent(MotionEvent event) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mStrokeMap.clear(); } for (int i = 0; i < event.getPointerCount(); i++) { Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); if (mStrokeMap.get(stroke) == null) { mStrokeMap.put(stroke, new Data()); } mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1)); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { decayValue(); mValue += mStrokeMap.get(stroke).getAnglesVariance(); } } } /** * Decreases mValue through time */ private void decayValue() { long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis - mLastUpdate > CLEAR_HISTORY) { mValue = 0.0f; } else { mValue *= Math.pow(HISTORY_FACTOR, (float) (currentTimeMillis - mLastUpdate) / INTERVAL); } mLastUpdate = currentTimeMillis; } @Override public void onSensorChanged(SensorEvent event) { } @Override public float getFalseTouchEvaluation(int type) { decayValue(); float currentValue = 0.0f; for (Data data: mStrokeMap.values()) { currentValue += data.getAnglesVariance(); } return (float) (mValue + currentValue); } private class Data { private List<Point> mLastThreePoints = new ArrayList<>(); private float mPreviousAngle; private float mSumSquares; private float mSum; private float mCount; public Data() { mPreviousAngle = (float) Math.PI; mSumSquares = 0.0f; mSum = 0.0f; mCount = 1.0f; } public void addPoint(Point point) { // Checking if the added point is different than the previously added point // Repetitions are being ignored so that proper angles are calculated. if (mLastThreePoints.isEmpty() || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) { mLastThreePoints.add(point); if (mLastThreePoints.size() == 4) { mLastThreePoints.remove(0); float angle = getAngle(mLastThreePoints.get(0), mLastThreePoints.get(1), mLastThreePoints.get(2)); float difference = angle - mPreviousAngle; mSum += difference; mSumSquares += difference * difference; mCount += 1.0; mPreviousAngle = angle; } } } private float getAngle(Point a, Point b, Point c) { float dist1 = a.dist(b); float dist2 = b.dist(c); float crossProduct = b.crossProduct(a, c); float dotProduct = b.dotProduct(a, c); float cos = Math.min(1.0f, Math.max(-1.0f, dotProduct / dist1 / dist2)); float angle = (float) Math.acos(cos); if (crossProduct < 0.0) { angle = 2.0f * (float) Math.PI - angle; } return angle; } public float getAnglesVariance() { return mSumSquares / mCount + (mSum / mCount) * (mSum / mCount); } } } No newline at end of file
packages/SystemUI/src/com/android/systemui/classifier/Classifier.java 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.hardware.SensorEvent; import android.view.MotionEvent; /** * An interface for classifiers for touch and sensor events. */ public abstract class Classifier { public static final int QUICK_SETTINGS = 0; public static final int NOTIFICATION_DISMISS = 1; public static final int NOTIFICATION_DRAG_DOWN = 2; public static final int NOTIFICATION_DOUBLE_TAP = 3; public static final int UNLOCK = 4; public static final int LEFT_AFFORDANCE = 5; public static final int RIGHT_AFFORDANCE = 6; /** * Contains all the information about touch events from which the classifier can query */ protected ClassifierData mClassifierData; /** * Informs the classifier that a new touch event has occurred */ public void onTouchEvent(MotionEvent event) { } /** * Informs the classifier that a sensor change occurred */ public void onSensorChanged(SensorEvent event) { } /** * @param type the type of action for which this method is called * @return a nonnegative value which is used to determine whether this a false touch. The * bigger the value the greater the chance that this a false touch. */ public abstract float getFalseTouchEvaluation(int type); }
packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.classifier; import android.util.SparseArray; import android.view.MotionEvent; /** * Contains data which is used to classify interaction sequences on the lockscreen. It does, for * example, provide information on the current touch state. */ public class ClassifierData { private SparseArray<Stroke> mCurrentStrokes = new SparseArray<>(); public ClassifierData() { } public void update(MotionEvent event) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mCurrentStrokes.clear(); } for (int i = 0; i < event.getPointerCount(); i++) { int id = event.getPointerId(i); if (mCurrentStrokes.get(id) == null) { mCurrentStrokes.put(id, new Stroke(event.getEventTimeNano())); } mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i), event.getEventTimeNano()); } } public void cleanUp(MotionEvent event) { int action = event.getActionMasked(); for (int i = 0; i < event.getPointerCount(); i++) { int id = event.getPointerId(i); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { mCurrentStrokes.remove(id); } } } /** * @param id the id from MotionEvent * @return the Stroke assigned to the id */ public Stroke getStroke(int id) { return mCurrentStrokes.get(id); } }