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