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