diff options
Diffstat (limited to 'java/com/android/incallui/answer/impl/classifier/AnglesClassifier.java')
-rw-r--r-- | java/com/android/incallui/answer/impl/classifier/AnglesClassifier.java | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/java/com/android/incallui/answer/impl/classifier/AnglesClassifier.java b/java/com/android/incallui/answer/impl/classifier/AnglesClassifier.java new file mode 100644 index 000000000..dbfbcfc1c --- /dev/null +++ b/java/com/android/incallui/answer/impl/classifier/AnglesClassifier.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 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.incallui.answer.impl.classifier; + +import android.util.ArrayMap; +import android.view.MotionEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 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. Then it calculates 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. + * + * <p>This classifier also tries to split the stroke into two parts in the place in which the + * biggest angle is. It calculates the angle variance of the two parts and sums them up. The reason + * the classifier is doing this, is because some human swipes at the beginning go for a moment in + * one direction and then they rapidly change direction for the rest of the stroke (like a tick). + * The final result is the minimum of angle variance of the whole stroke and the sum of angle + * variances of the two parts split up. The classifier tries the tick option only if the first part + * is shorter than the second part. + * + * <p>Additionally, the classifier classifies the angles as left angles (those angles which value is + * in [0.0, PI - ANGLE_DEVIATION) interval), straight angles ([PI - ANGLE_DEVIATION, PI + + * ANGLE_DEVIATION] interval) and right angles ((PI + ANGLE_DEVIATION, 2 * PI) interval) and then + * calculates the percentage of angles which are in the same direction (straight angles can be left + * angels or right angles) + */ +class AnglesClassifier extends StrokeClassifier { + private Map<Stroke, Data> mStrokeMap = new ArrayMap<>(); + + public AnglesClassifier(ClassifierData classifierData) { + mClassifierData = classifierData; + } + + @Override + public String getTag() { + return "ANG"; + } + + @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)); + } + } + + @Override + public float getFalseTouchEvaluation(Stroke stroke) { + Data data = mStrokeMap.get(stroke); + return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance()) + + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage()); + } + + private static class Data { + private static final float ANGLE_DEVIATION = (float) Math.PI / 20.0f; + private static final float MIN_MOVE_DIST_DP = .01f; + + private List<Point> mLastThreePoints = new ArrayList<>(); + private float mFirstAngleVariance; + private float mPreviousAngle; + private float mBiggestAngle; + private float mSumSquares; + private float mSecondSumSquares; + private float mSum; + private float mSecondSum; + private float mCount; + private float mSecondCount; + private float mFirstLength; + private float mLength; + private float mAnglesCount; + private float mLeftAngles; + private float mRightAngles; + private float mStraightAngles; + + public Data() { + mFirstAngleVariance = 0.0f; + mPreviousAngle = (float) Math.PI; + mBiggestAngle = 0.0f; + mSumSquares = mSecondSumSquares = 0.0f; + mSum = mSecondSum = 0.0f; + mCount = mSecondCount = 1.0f; + mLength = mFirstLength = 0.0f; + mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f; + } + + public void addPoint(Point point) { + // Checking if the added point is different than the previously added point + // Repetitions and short distances are being ignored so that proper angles are calculated. + if (mLastThreePoints.isEmpty() + || (!mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point) + && (mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point) + > MIN_MOVE_DIST_DP))) { + if (!mLastThreePoints.isEmpty()) { + mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point); + } + mLastThreePoints.add(point); + if (mLastThreePoints.size() == 4) { + mLastThreePoints.remove(0); + + float angle = + mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0), mLastThreePoints.get(2)); + + mAnglesCount++; + if (angle < Math.PI - ANGLE_DEVIATION) { + mLeftAngles++; + } else if (angle <= Math.PI + ANGLE_DEVIATION) { + mStraightAngles++; + } else { + mRightAngles++; + } + + float difference = angle - mPreviousAngle; + + // If this is the biggest angle of the stroke so then we save the value of + // the angle variance so far and start to count the values for the angle + // variance of the second part. + if (mBiggestAngle < angle) { + mBiggestAngle = angle; + mFirstLength = mLength; + mFirstAngleVariance = getAnglesVariance(mSumSquares, mSum, mCount); + mSecondSumSquares = 0.0f; + mSecondSum = 0.0f; + mSecondCount = 1.0f; + } else { + mSecondSum += difference; + mSecondSumSquares += difference * difference; + mSecondCount += 1.0f; + } + + mSum += difference; + mSumSquares += difference * difference; + mCount += 1.0f; + mPreviousAngle = angle; + } + } + } + + public float getAnglesVariance(float sumSquares, float sum, float count) { + return sumSquares / count - (sum / count) * (sum / count); + } + + public float getAnglesVariance() { + float anglesVariance = getAnglesVariance(mSumSquares, mSum, mCount); + if (mFirstLength < mLength / 2f) { + anglesVariance = + Math.min( + anglesVariance, + mFirstAngleVariance + + getAnglesVariance(mSecondSumSquares, mSecondSum, mSecondCount)); + } + return anglesVariance; + } + + public float getAnglesPercentage() { + if (mAnglesCount == 0.0f) { + return 1.0f; + } + return (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount; + } + } +} |