1ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/* 2ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Copyright (C) 2016 The Android Open Source Project 3ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 4ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License"); 5ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * you may not use this file except in compliance with the License. 6ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * You may obtain a copy of the License at 7ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 8ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * http://www.apache.org/licenses/LICENSE-2.0 9ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 10ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Unless required by applicable law or agreed to in writing, software 11ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS, 12ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * See the License for the specific language governing permissions and 14ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * limitations under the License 15ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 16ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 17ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpackage com.android.incallui.answer.impl.classifier; 18ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 19ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.util.ArrayMap; 20ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.MotionEvent; 21ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.util.ArrayList; 22ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.util.List; 23ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.util.Map; 24ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 25ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/** 26ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * A classifier which calculates the variance of differences between successive angles in a stroke. 27ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * For each stroke it keeps its last three points. If some successive points are the same, it 28ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * ignores the repetitions. If a new point is added, the classifier calculates the angle between the 29ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * last three points. After that, it calculates the difference between this angle and the previously 30ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * calculated angle. Then it calculates the variance of the differences from a stroke. To the 31ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * differences there is artificially added value 0.0 and the difference between the first angle and 32ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * PI (angles are in radians). It helps with strokes which have few points and punishes more strokes 33ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * which are not smooth. 34ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 35ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * <p>This classifier also tries to split the stroke into two parts in the place in which the 36ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * biggest angle is. It calculates the angle variance of the two parts and sums them up. The reason 37ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * the classifier is doing this, is because some human swipes at the beginning go for a moment in 38ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * one direction and then they rapidly change direction for the rest of the stroke (like a tick). 39ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * The final result is the minimum of angle variance of the whole stroke and the sum of angle 40ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * variances of the two parts split up. The classifier tries the tick option only if the first part 41ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * is shorter than the second part. 42ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 43ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * <p>Additionally, the classifier classifies the angles as left angles (those angles which value is 44ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * in [0.0, PI - ANGLE_DEVIATION) interval), straight angles ([PI - ANGLE_DEVIATION, PI + 45ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * ANGLE_DEVIATION] interval) and right angles ((PI + ANGLE_DEVIATION, 2 * PI) interval) and then 46ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * calculates the percentage of angles which are in the same direction (straight angles can be left 47ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * angels or right angles) 48ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 49ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianclass AnglesClassifier extends StrokeClassifier { 50ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private Map<Stroke, Data> mStrokeMap = new ArrayMap<>(); 51ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 52ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public AnglesClassifier(ClassifierData classifierData) { 53ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mClassifierData = classifierData; 54ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 55ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 56ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 57ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public String getTag() { 58ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return "ANG"; 59ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 60ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 61ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 62ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onTouchEvent(MotionEvent event) { 63ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int action = event.getActionMasked(); 64ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 65ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (action == MotionEvent.ACTION_DOWN) { 66ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mStrokeMap.clear(); 67ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 68ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 69ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian for (int i = 0; i < event.getPointerCount(); i++) { 70ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); 71ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 72ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mStrokeMap.get(stroke) == null) { 73ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mStrokeMap.put(stroke, new Data()); 74ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 75ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1)); 76ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 77ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 78ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 79ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 80ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public float getFalseTouchEvaluation(Stroke stroke) { 81ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Data data = mStrokeMap.get(stroke); 82ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance()) 83ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage()); 84ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 85ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 86ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static class Data { 87ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final float ANGLE_DEVIATION = (float) Math.PI / 20.0f; 88ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final float MIN_MOVE_DIST_DP = .01f; 89ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 90ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private List<Point> mLastThreePoints = new ArrayList<>(); 91ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mFirstAngleVariance; 92ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mPreviousAngle; 93ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mBiggestAngle; 94ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mSumSquares; 95ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mSecondSumSquares; 96ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mSum; 97ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mSecondSum; 98ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mCount; 99ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mSecondCount; 100ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mFirstLength; 101ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mLength; 102ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mAnglesCount; 103ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mLeftAngles; 104ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mRightAngles; 105ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float mStraightAngles; 106ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 107ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Data() { 108ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mFirstAngleVariance = 0.0f; 109ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mPreviousAngle = (float) Math.PI; 110ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mBiggestAngle = 0.0f; 111ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSumSquares = mSecondSumSquares = 0.0f; 112ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSum = mSecondSum = 0.0f; 113ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mCount = mSecondCount = 1.0f; 114ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLength = mFirstLength = 0.0f; 115ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f; 116ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 117ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 118ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void addPoint(Point point) { 119ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // Checking if the added point is different than the previously added point 120ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // Repetitions and short distances are being ignored so that proper angles are calculated. 121ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mLastThreePoints.isEmpty() 122ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian || (!mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point) 123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian && (mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point) 124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian > MIN_MOVE_DIST_DP))) { 125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!mLastThreePoints.isEmpty()) { 126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point); 127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLastThreePoints.add(point); 129ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mLastThreePoints.size() == 4) { 130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLastThreePoints.remove(0); 131ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float angle = 133ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0), mLastThreePoints.get(2)); 134ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 135ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mAnglesCount++; 136ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (angle < Math.PI - ANGLE_DEVIATION) { 137ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mLeftAngles++; 138ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else if (angle <= Math.PI + ANGLE_DEVIATION) { 139ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mStraightAngles++; 140ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 141ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mRightAngles++; 142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 143ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 144ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float difference = angle - mPreviousAngle; 145ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 146ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // If this is the biggest angle of the stroke so then we save the value of 147ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // the angle variance so far and start to count the values for the angle 148ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // variance of the second part. 149ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mBiggestAngle < angle) { 150ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mBiggestAngle = angle; 151ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mFirstLength = mLength; 152ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mFirstAngleVariance = getAnglesVariance(mSumSquares, mSum, mCount); 153ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondSumSquares = 0.0f; 154ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondSum = 0.0f; 155ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondCount = 1.0f; 156ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 157ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondSum += difference; 158ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondSumSquares += difference * difference; 159ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSecondCount += 1.0f; 160ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 161ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 162ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSum += difference; 163ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mSumSquares += difference * difference; 164ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mCount += 1.0f; 165ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mPreviousAngle = angle; 166ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 167ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 168ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 169ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 170ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public float getAnglesVariance(float sumSquares, float sum, float count) { 171ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return sumSquares / count - (sum / count) * (sum / count); 172ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 173ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 174ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public float getAnglesVariance() { 175ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float anglesVariance = getAnglesVariance(mSumSquares, mSum, mCount); 176ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mFirstLength < mLength / 2f) { 177ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian anglesVariance = 178ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Math.min( 179ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian anglesVariance, 180ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mFirstAngleVariance 181ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian + getAnglesVariance(mSecondSumSquares, mSecondSum, mSecondCount)); 182ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 183ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return anglesVariance; 184ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 185ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 186ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public float getAnglesPercentage() { 187ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mAnglesCount == 0.0f) { 188ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return 1.0f; 189ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 190ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount; 191ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 192ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 193ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian} 194