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 52 public AnglesClassifier(ClassifierData classifierData) { 53 mClassifierData = classifierData; 54 } 55 56 @Override 57 public String getTag() { 58 return "ANG"; 59 } 60 61 @Override 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 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 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 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 167 public float getAnglesVariance(float sumSquares, float sum, float count) { 168 return sumSquares / count - (sum / count) * (sum / count); 169 } 170 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 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 }