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.os.Build; 20 import android.os.SystemProperties; 21 import android.util.Log; 22 import android.view.MotionEvent; 23 24 import java.util.ArrayList; 25 import java.util.HashMap; 26 import java.util.List; 27 28 /** 29 * A classifier which calculates the variance of differences between successive angles in a stroke. 30 * For each stroke it keeps its last three points. If some successive points are the same, it 31 * ignores the repetitions. If a new point is added, the classifier calculates the angle between 32 * the last three points. After that, it calculates the difference between this angle and the 33 * previously calculated angle. Then it calculates the variance of the differences from a stroke. 34 * To the differences there is artificially added value 0.0 and the difference between the first 35 * angle and PI (angles are in radians). It helps with strokes which have few points and punishes 36 * more strokes which are not smooth. 37 * 38 * This classifier also tries to split the stroke into two parts in the place in which the biggest 39 * angle is. It calculates the angle variance of the two parts and sums them up. The reason the 40 * classifier is doing this, is because some human swipes at the beginning go for a moment in one 41 * direction and then they rapidly change direction for the rest of the stroke (like a tick). The 42 * final result is the minimum of angle variance of the whole stroke and the sum of angle variances 43 * of the two parts split up. The classifier tries the tick option only if the first part is 44 * shorter than the second part. 45 * 46 * Additionally, the classifier classifies the angles as left angles (those angles which value is 47 * in [0.0, PI - ANGLE_DEVIATION) interval), straight angles 48 * ([PI - ANGLE_DEVIATION, PI + ANGLE_DEVIATION] interval) and right angles 49 * ((PI + ANGLE_DEVIATION, 2 * PI) interval) and then calculates the percentage of angles which are 50 * in the same direction (straight angles can be left angels or right angles) 51 */ 52 public class AnglesClassifier extends StrokeClassifier { 53 private HashMap<Stroke, Data> mStrokeMap = new HashMap<>(); 54 55 public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.ang", 56 Build.IS_DEBUGGABLE); 57 58 private static String TAG = "ANG"; 59 AnglesClassifier(ClassifierData classifierData)60 public AnglesClassifier(ClassifierData classifierData) { 61 mClassifierData = classifierData; 62 } 63 64 @Override getTag()65 public String getTag() { 66 return TAG; 67 } 68 69 @Override onTouchEvent(MotionEvent event)70 public void onTouchEvent(MotionEvent event) { 71 int action = event.getActionMasked(); 72 73 if (action == MotionEvent.ACTION_DOWN) { 74 mStrokeMap.clear(); 75 } 76 77 for (int i = 0; i < event.getPointerCount(); i++) { 78 Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); 79 80 if (mStrokeMap.get(stroke) == null) { 81 mStrokeMap.put(stroke, new Data()); 82 } 83 mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1)); 84 } 85 } 86 87 @Override getFalseTouchEvaluation(int type, Stroke stroke)88 public float getFalseTouchEvaluation(int type, Stroke stroke) { 89 Data data = mStrokeMap.get(stroke); 90 return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance(), type) 91 + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage(), type); 92 } 93 94 private static class Data { 95 private final float ANGLE_DEVIATION = (float) Math.PI / 20.0f; 96 97 private List<Point> mLastThreePoints = new ArrayList<>(); 98 private float mFirstAngleVariance; 99 private float mPreviousAngle; 100 private float mBiggestAngle; 101 private float mSumSquares; 102 private float mSecondSumSquares; 103 private float mSum; 104 private float mSecondSum; 105 private float mCount; 106 private float mSecondCount; 107 private float mFirstLength; 108 private float mLength; 109 private float mAnglesCount; 110 private float mLeftAngles; 111 private float mRightAngles; 112 private float mStraightAngles; 113 Data()114 public Data() { 115 mFirstAngleVariance = 0.0f; 116 mPreviousAngle = (float) Math.PI; 117 mBiggestAngle = 0.0f; 118 mSumSquares = mSecondSumSquares = 0.0f; 119 mSum = mSecondSum = 0.0f; 120 mCount = mSecondCount = 1.0f; 121 mLength = mFirstLength = 0.0f; 122 mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f; 123 } 124 addPoint(Point point)125 public void addPoint(Point point) { 126 // Checking if the added point is different than the previously added point 127 // Repetitions are being ignored so that proper angles are calculated. 128 if (mLastThreePoints.isEmpty() 129 || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) { 130 if (!mLastThreePoints.isEmpty()) { 131 mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point); 132 } 133 mLastThreePoints.add(point); 134 if (mLastThreePoints.size() == 4) { 135 mLastThreePoints.remove(0); 136 137 float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0), 138 mLastThreePoints.get(2)); 139 140 mAnglesCount++; 141 if (angle < Math.PI - ANGLE_DEVIATION) { 142 mLeftAngles++; 143 } else if (angle <= Math.PI + ANGLE_DEVIATION) { 144 mStraightAngles++; 145 } else { 146 mRightAngles++; 147 } 148 149 float difference = angle - mPreviousAngle; 150 151 // If this is the biggest angle of the stroke so then we save the value of 152 // the angle variance so far and start to count the values for the angle 153 // variance of the second part. 154 if (mBiggestAngle < angle) { 155 mBiggestAngle = angle; 156 mFirstLength = mLength; 157 mFirstAngleVariance = getAnglesVariance(mSumSquares, mSum, mCount); 158 mSecondSumSquares = 0.0f; 159 mSecondSum = 0.0f; 160 mSecondCount = 1.0f; 161 } else { 162 mSecondSum += difference; 163 mSecondSumSquares += difference * difference; 164 mSecondCount += 1.0; 165 } 166 167 mSum += difference; 168 mSumSquares += difference * difference; 169 mCount += 1.0; 170 mPreviousAngle = angle; 171 } 172 } 173 } 174 getAnglesVariance(float sumSquares, float sum, float count)175 public float getAnglesVariance(float sumSquares, float sum, float count) { 176 return sumSquares / count - (sum / count) * (sum / count); 177 } 178 getAnglesVariance()179 public float getAnglesVariance() { 180 float anglesVariance = getAnglesVariance(mSumSquares, mSum, mCount); 181 if (VERBOSE) { 182 FalsingLog.i(TAG, "getAnglesVariance: (first pass) " + anglesVariance); 183 FalsingLog.i(TAG, " - mFirstLength=" + mFirstLength); 184 FalsingLog.i(TAG, " - mLength=" + mLength); 185 } 186 if (mFirstLength < mLength / 2f) { 187 anglesVariance = Math.min(anglesVariance, mFirstAngleVariance 188 + getAnglesVariance(mSecondSumSquares, mSecondSum, mSecondCount)); 189 if (VERBOSE) FalsingLog.i(TAG, "getAnglesVariance: (second pass) " + anglesVariance); 190 } 191 return anglesVariance; 192 } 193 getAnglesPercentage()194 public float getAnglesPercentage() { 195 if (mAnglesCount == 0.0f) { 196 if (VERBOSE) FalsingLog.i(TAG, "getAnglesPercentage: count==0, result=1"); 197 return 1.0f; 198 } 199 final float result = (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount; 200 if (VERBOSE) { 201 FalsingLog.i(TAG, "getAnglesPercentage: left=" + mLeftAngles + " right=" 202 + mRightAngles + " straight=" + mStraightAngles + " count=" + mAnglesCount 203 + " result=" + result); 204 } 205 return result; 206 } 207 } 208 } 209