diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java index 5cd914f068289..8c681fc8def1b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceClassifier.java @@ -16,7 +16,6 @@ package com.android.systemui.classifier; -import android.hardware.SensorEvent; import android.view.MotionEvent; import java.lang.Math; @@ -27,27 +26,19 @@ 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. + * 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. To the differences there is artificially added value 0.0 and the + * difference between the first angle and PI (angles are in radians). It helps with strokes which + * have few points and punishes more strokes which are not smooth. */ -public class AnglesVarianceClassifier extends Classifier { - private final float INTERVAL = 10.0f; - private final float CLEAR_HISTORY = 500f; - private final float HISTORY_FACTOR = 0.9f; - +public class AnglesVarianceClassifier extends StrokeClassifier { private HashMap mStrokeMap = new HashMap<>(); - private float mValue; - private long mLastUpdate; public AnglesVarianceClassifier(ClassifierData classifierData) { mClassifierData = classifierData; - mValue = 0.0f; - mLastUpdate = System.currentTimeMillis(); } @Override @@ -65,40 +56,12 @@ public class AnglesVarianceClassifier extends Classifier { 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); + public float getFalseTouchEvaluation(int type, Stroke stroke) { + return mStrokeMap.get(stroke).getAnglesVariance(); } private class Data { @@ -150,7 +113,7 @@ public class AnglesVarianceClassifier extends Classifier { } public float getAnglesVariance() { - return mSumSquares / mCount + (mSum / mCount) * (mSum / mCount); + return mSumSquares / mCount - (mSum / mCount) * (mSum / mCount); } } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java index b76be1478bb48..89d20defc7dbb 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java @@ -20,7 +20,7 @@ import android.hardware.SensorEvent; import android.view.MotionEvent; /** - * An interface for classifiers for touch and sensor events. + * An abstract class for classifiers for touch and sensor events. */ public abstract class Classifier { public static final int QUICK_SETTINGS = 0; @@ -30,6 +30,7 @@ public abstract class Classifier { public static final int UNLOCK = 4; public static final int LEFT_AFFORDANCE = 5; public static final int RIGHT_AFFORDANCE = 6; + public static final int GENERIC = 7; /** * Contains all the information about touch events from which the classifier can query @@ -47,11 +48,4 @@ public abstract class Classifier { */ 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); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java index 77b81d2b68fa8..bccad4e94235b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java @@ -19,21 +19,26 @@ package com.android.systemui.classifier; import android.util.SparseArray; import android.view.MotionEvent; +import java.util.ArrayList; + /** * 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 mCurrentStrokes = new SparseArray<>(); + private ArrayList mEndingStrokes = new ArrayList<>(); public ClassifierData() { } public void update(MotionEvent event) { + mEndingStrokes.clear(); 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) { @@ -41,10 +46,16 @@ public class ClassifierData { } mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i), event.getEventTimeNano()); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL + || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { + mEndingStrokes.add(getStroke(id)); + } } } public void cleanUp(MotionEvent event) { + mEndingStrokes.clear(); int action = event.getActionMasked(); for (int i = 0; i < event.getPointerCount(); i++) { int id = event.getPointerId(i); @@ -55,6 +66,13 @@ public class ClassifierData { } } + /** + * @return the list of Strokes which are ending in the recently added MotionEvent + */ + public ArrayList getEndingStrokes() { + return mEndingStrokes; + } + /** * @param id the id from MotionEvent * @return the Stroke assigned to the id diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java index 347273ac0f68b..c68fff837c6ff 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java @@ -126,11 +126,10 @@ public class FalsingManager implements SensorEventListener { } /** - * @param type the type of action for which this method is called * @return true if the classifier determined that this is not a human interacting with the phone */ - public boolean isFalseTouch(int type) { - return mHumanInteractionClassifier.getFalseTouchEvaluation(type) > 0.5; + public boolean isFalseTouch() { + return mHumanInteractionClassifier.isFalseTouch(); } @Override @@ -189,6 +188,7 @@ public class FalsingManager implements SensorEventListener { } public void onQsDown() { + mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS); mDataCollector.onQsDown(); } @@ -197,6 +197,7 @@ public class FalsingManager implements SensorEventListener { } public void onTrackingStarted() { + mHumanInteractionClassifier.setType(Classifier.UNLOCK); mDataCollector.onTrackingStarted(); } @@ -217,6 +218,7 @@ public class FalsingManager implements SensorEventListener { } public void onNotificatonStartDraggingDown() { + mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN); mDataCollector.onNotificatonStartDraggingDown(); } @@ -229,6 +231,7 @@ public class FalsingManager implements SensorEventListener { } public void onNotificatonStartDismissing() { + mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS); mDataCollector.onNotificatonStartDismissing(); } @@ -245,6 +248,11 @@ public class FalsingManager implements SensorEventListener { } public void onAffordanceSwipingStarted(boolean rightCorner) { + if (rightCorner) { + mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE); + } else { + mHumanInteractionClassifier.setType(Classifier.LEFT_AFFORDANCE); + } mDataCollector.onAffordanceSwipingStarted(rightCorner); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java new file mode 100644 index 0000000000000..e7f4c35ed44d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * An abstract class for classifiers which classify the whole gesture (all the strokes which + * occurred from DOWN event to UP/CANCEL event) + */ +public abstract class GestureClassifier extends Classifier { + + /** + * @param type the type of action for which this method is called + * @return a non-negative value which is used to determine whether the most recent gesture is a + * false interaction. The bigger the value the greater the chance that this a false + * interaction. + */ + public abstract float getFalseTouchEvaluation(int type); +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java new file mode 100644 index 0000000000000..b057bdaa9d97a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java @@ -0,0 +1,112 @@ +/* + * 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 java.util.ArrayList; + +/** + * Holds the evaluations for ended strokes and gestures. These values are decreased through time. + */ +public class HistoryEvaluator { + private static final float INTERVAL = 50.0f; + private static final float HISTORY_FACTOR = 0.9f; + private static final float EPSILON = 1e-5f; + + private final ArrayList mStrokes = new ArrayList<>(); + private final ArrayList mGestureWeights = new ArrayList<>(); + private long mLastUpdate; + + public HistoryEvaluator() { + mLastUpdate = System.currentTimeMillis(); + } + + public void addStroke(float evaluation) { + decayValue(); + mStrokes.add(new Data(evaluation)); + } + + public void addGesture(float evaluation) { + decayValue(); + mGestureWeights.add(new Data(evaluation)); + } + + /** + * Calculates the weighted average of strokes and adds to it the weighted average of gestures + */ + public float getEvaluation() { + return weightedAverage(mStrokes) + weightedAverage(mGestureWeights); + } + + private float weightedAverage(ArrayList list) { + float sumValue = 0.0f; + float sumWeight = 0.0f; + int size = list.size(); + for (int i = 0; i < size; i++) { + Data data = list.get(i); + sumValue += data.evaluation * data.weight; + sumWeight += data.weight; + } + + if (sumWeight == 0.0f) { + return 0.0f; + } + + return sumValue / sumWeight; + } + + private void decayValue() { + long currentTimeMillis = System.currentTimeMillis(); + + // All weights are multiplied by HISTORY_FACTOR after each INTERVAL milliseconds. + float factor = (float) Math.pow(HISTORY_FACTOR, + (float) (currentTimeMillis - mLastUpdate) / INTERVAL); + + decayValue(mStrokes, factor); + decayValue(mGestureWeights, factor); + mLastUpdate = currentTimeMillis; + } + + private void decayValue(ArrayList list, float factor) { + int size = list.size(); + for (int i = 0; i < size; i++) { + list.get(i).weight *= factor; + } + + // Removing evaluations with such small weights that they do not matter anymore + while (!list.isEmpty() && isZero(list.get(0).weight)) { + list.remove(0); + } + } + + private boolean isZero(float x) { + return x <= EPSILON && x >= -EPSILON; + } + + /** + * For each stroke it holds its initial value and the current weight. Initially the + * weight is set to 1.0 + */ + private class Data { + public float evaluation; + public float weight; + + public Data(float evaluation) { + this.evaluation = evaluation; + weight = 1.0f; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java index a5f6df858d2ca..86ea640c925ee 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java @@ -25,6 +25,8 @@ import android.os.UserHandle; import android.provider.Settings; import android.view.MotionEvent; +import java.util.ArrayList; + /** * An classifier trying to determine whether it is a human interacting with the phone or not. */ @@ -35,8 +37,14 @@ public class HumanInteractionClassifier extends Classifier { private final Handler mHandler = new Handler(); private final Context mContext; - private AnglesVarianceClassifier mAnglesVarianceClassifier; + private ArrayList mStrokeClassifiers = new ArrayList<>(); + private ArrayList mGestureClassifiers = new ArrayList<>(); + private final int mStrokeClassifiersSize; + private final int mGestureClassifiersSize; + + private HistoryEvaluator mHistoryEvaluator; private boolean mEnableClassifier = false; + private int mCurrentType = Classifier.GENERIC; protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override @@ -48,7 +56,12 @@ public class HumanInteractionClassifier extends Classifier { private HumanInteractionClassifier(Context context) { mContext = context; mClassifierData = new ClassifierData(); - mAnglesVarianceClassifier = new AnglesVarianceClassifier(mClassifierData); + mHistoryEvaluator = new HistoryEvaluator(); + + mStrokeClassifiers.add(new AnglesVarianceClassifier(mClassifierData)); + + mStrokeClassifiersSize = mStrokeClassifiers.size(); + mGestureClassifiersSize = mGestureClassifiers.size(); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(HIC_ENABLE), false, @@ -71,11 +84,44 @@ public class HumanInteractionClassifier extends Classifier { HIC_ENABLE, 0); } + public void setType(int type) { + mCurrentType = type; + } + @Override public void onTouchEvent(MotionEvent event) { if (mEnableClassifier) { mClassifierData.update(event); - mAnglesVarianceClassifier.onTouchEvent(event); + + for (int i = 0; i < mStrokeClassifiersSize; i++) { + mStrokeClassifiers.get(i).onTouchEvent(event); + } + + for (int i = 0; i < mGestureClassifiersSize; i++) { + mGestureClassifiers.get(i).onTouchEvent(event); + } + + int size = mClassifierData.getEndingStrokes().size(); + for (int i = 0; i < size; i++) { + Stroke stroke = mClassifierData.getEndingStrokes().get(i); + float evaluation = 0.0f; + for (int j = 0; j < mStrokeClassifiersSize; j++) { + evaluation += mStrokeClassifiers.get(j).getFalseTouchEvaluation( + mCurrentType, stroke); + } + mHistoryEvaluator.addStroke(evaluation); + } + + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + float evaluation = 0.0f; + for (int i = 0; i < mGestureClassifiersSize; i++) { + evaluation += mGestureClassifiers.get(i).getFalseTouchEvaluation(mCurrentType); + } + mHistoryEvaluator.addGesture(evaluation); + setType(Classifier.GENERIC); + } + mClassifierData.cleanUp(event); } } @@ -84,12 +130,8 @@ public class HumanInteractionClassifier extends Classifier { public void onSensorChanged(SensorEvent event) { } - @Override - public float getFalseTouchEvaluation(int type) { - if (mEnableClassifier) { - return mAnglesVarianceClassifier.getFalseTouchEvaluation(type); - } - return 0.0f; + public boolean isFalseTouch() { + return mHistoryEvaluator.getEvaluation() >= 5.0f; } public boolean isEnabled() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java index f386cbe458e67..8c3fdd45e7d60 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java @@ -19,7 +19,8 @@ package com.android.systemui.classifier; import java.util.ArrayList; /** - * Contains data about movement traces (pointers) + * Contains data about a stroke (a single trace, all the events from a given id from the + * DOWN/POINTER_DOWN event till the UP/POINTER_UP/CANCEL event.) */ public class Stroke { private ArrayList mPoints = new ArrayList<>(); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java new file mode 100644 index 0000000000000..d561f460c079c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * An abstract class for classifiers which classify each stroke separately. + */ +public abstract class StrokeClassifier extends Classifier { + + /** + * @param type the type of action for which this method is called + * @param stroke the stroke for which the evaluation will be calculated + * @return a non-negative 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, Stroke stroke); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 13d0e1e0fbc38..3feead8f36ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -610,7 +610,7 @@ public abstract class PanelView extends FrameLayout { if (!mStatusBar.isFalsingThresholdNeeded()) { return false; } - if (mFalsingManager.isFalseTouch(Classifier.UNLOCK)) { + if (mFalsingManager.isFalseTouch()) { return true; } if (!mTouchAboveFalsingThreshold) {