Merge "Add HistoryEvaluator"
This commit is contained in:
committed by
Android (Google) Code Review
commit
af4e36122a
@@ -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<Stroke, Data> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Stroke> mCurrentStrokes = new SparseArray<>();
|
||||
private ArrayList<Stroke> 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<Stroke> getEndingStrokes() {
|
||||
return mEndingStrokes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id from MotionEvent
|
||||
* @return the Stroke assigned to the id
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Data> mStrokes = new ArrayList<>();
|
||||
private final ArrayList<Data> 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<Data> 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<Data> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<StrokeClassifier> mStrokeClassifiers = new ArrayList<>();
|
||||
private ArrayList<GestureClassifier> 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() {
|
||||
|
||||
@@ -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<Point> mPoints = new ArrayList<>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user