blob: 526e5fa78d355702cf0f748d7bd0b1f92f6ecb95 [file] [log] [blame]
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -07001/*
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
17package com.android.systemui.classifier;
18
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070019import android.view.MotionEvent;
20
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070021import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.List;
24
25/**
26 * A classifier which calculates the variance of differences between successive angles in a stroke.
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070027 * 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
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070030 * 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
Blazej Magnowskia28de572015-09-25 16:41:27 -070033 * 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)
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070048 */
Blazej Magnowskia28de572015-09-25 16:41:27 -070049public class AnglesClassifier extends StrokeClassifier {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070050 private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070051
Blazej Magnowskia28de572015-09-25 16:41:27 -070052 public AnglesClassifier(ClassifierData classifierData) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070053 mClassifierData = classifierData;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070054 }
55
56 @Override
Adrian Roos401caae2016-03-04 13:35:21 -080057 public String getTag() {
58 return "ANG";
59 }
60
61 @Override
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070062 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));
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070076 }
77 }
78
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070079 @Override
Blazej Magnowski9f01c5b2015-09-17 15:14:29 -070080 public float getFalseTouchEvaluation(int type, Stroke stroke) {
Blazej Magnowskia28de572015-09-25 16:41:27 -070081 Data data = mStrokeMap.get(stroke);
82 return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance())
83 + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage());
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070084 }
85
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070086 private static class Data {
Blazej Magnowskia28de572015-09-25 16:41:27 -070087 private final float ANGLE_DEVIATION = (float) Math.PI / 20.0f;
88
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070089 private List<Point> mLastThreePoints = new ArrayList<>();
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070090 private float mFirstAngleVariance;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070091 private float mPreviousAngle;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070092 private float mBiggestAngle;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070093 private float mSumSquares;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070094 private float mSecondSumSquares;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070095 private float mSum;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070096 private float mSecondSum;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070097 private float mCount;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -070098 private float mSecondCount;
Blazej Magnowskia28de572015-09-25 16:41:27 -070099 private float mFirstLength;
100 private float mLength;
101 private float mAnglesCount;
102 private float mLeftAngles;
103 private float mRightAngles;
104 private float mStraightAngles;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700105
106 public Data() {
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700107 mFirstAngleVariance = 0.0f;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700108 mPreviousAngle = (float) Math.PI;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700109 mBiggestAngle = 0.0f;
110 mSumSquares = mSecondSumSquares = 0.0f;
111 mSum = mSecondSum = 0.0f;
112 mCount = mSecondCount = 1.0f;
Blazej Magnowskia28de572015-09-25 16:41:27 -0700113 mLength = mFirstLength = 0.0f;
114 mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700115 }
116
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)) {
Blazej Magnowskia28de572015-09-25 16:41:27 -0700122 if (!mLastThreePoints.isEmpty()) {
123 mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point);
124 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700125 mLastThreePoints.add(point);
126 if (mLastThreePoints.size() == 4) {
127 mLastThreePoints.remove(0);
128
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700129 float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0),
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700130 mLastThreePoints.get(2));
131
Blazej Magnowskia28de572015-09-25 16:41:27 -0700132 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
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700141 float difference = angle - mPreviousAngle;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700142
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;
Blazej Magnowskia28de572015-09-25 16:41:27 -0700148 mFirstLength = mLength;
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700149 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
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700159 mSum += difference;
160 mSumSquares += difference * difference;
161 mCount += 1.0;
162 mPreviousAngle = angle;
163 }
164 }
165 }
166
Blazej Magnowski68d0c9b2015-09-18 17:25:55 -0700167 public float getAnglesVariance(float sumSquares, float sum, float count) {
168 return sumSquares / count - (sum / count) * (sum / count);
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700169 }
170
171 public float getAnglesVariance() {
Blazej Magnowskia28de572015-09-25 16:41:27 -0700172 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
180 public float getAnglesPercentage() {
181 if (mAnglesCount == 0.0f) {
182 return 1.0f;
183 }
184 return (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700185 }
186 }
187}