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.incallui.answer.impl.classifier; 18 19 import android.util.ArrayMap; 20 import android.view.MotionEvent; 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.Map; 24 25 /** 26 * A classifier which for each point from a stroke, it creates a point on plane with coordinates 27 * (timeOffsetNano, distanceCoveredUpToThisPoint) (scaled by DURATION_SCALE and LENGTH_SCALE) and 28 * then it calculates the angle variance of these points like the class {@link AnglesClassifier} 29 * (without splitting it into two parts). The classifier ignores the last point of a stroke because 30 * the UP event comes in with some delay and this ruins the smoothness of this curve. Additionally, 31 * the classifier classifies calculates the percentage of angles which value is in [PI - 32 * ANGLE_DEVIATION, 2* PI) interval. The reason why the classifier does that is because the speed of 33 * a good stroke is most often increases, so most of these angels should be in this interval. 34 */ 35 class SpeedAnglesClassifier extends StrokeClassifier { 36 private Map<Stroke, Data> mStrokeMap = new ArrayMap<>(); 37 SpeedAnglesClassifier(ClassifierData classifierData)38 public SpeedAnglesClassifier(ClassifierData classifierData) { 39 mClassifierData = classifierData; 40 } 41 42 @Override getTag()43 public String getTag() { 44 return "SPD_ANG"; 45 } 46 47 @Override onTouchEvent(MotionEvent event)48 public void onTouchEvent(MotionEvent event) { 49 int action = event.getActionMasked(); 50 51 if (action == MotionEvent.ACTION_DOWN) { 52 mStrokeMap.clear(); 53 } 54 55 for (int i = 0; i < event.getPointerCount(); i++) { 56 Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); 57 58 if (mStrokeMap.get(stroke) == null) { 59 mStrokeMap.put(stroke, new Data()); 60 } 61 62 if (action != MotionEvent.ACTION_UP 63 && action != MotionEvent.ACTION_CANCEL 64 && !(action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { 65 mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1)); 66 } 67 } 68 } 69 70 @Override getFalseTouchEvaluation(Stroke stroke)71 public float getFalseTouchEvaluation(Stroke stroke) { 72 Data data = mStrokeMap.get(stroke); 73 return SpeedVarianceEvaluator.evaluate(data.getAnglesVariance()) 74 + SpeedAnglesPercentageEvaluator.evaluate(data.getAnglesPercentage()); 75 } 76 77 private static class Data { 78 private static final float DURATION_SCALE = 1e8f; 79 private static final float LENGTH_SCALE = 1.0f; 80 private static final float ANGLE_DEVIATION = (float) Math.PI / 10.0f; 81 82 private List<Point> mLastThreePoints = new ArrayList<>(); 83 private Point mPreviousPoint; 84 private float mPreviousAngle; 85 private float mSumSquares; 86 private float mSum; 87 private float mCount; 88 private float mDist; 89 private float mAnglesCount; 90 private float mAcceleratingAngles; 91 Data()92 public Data() { 93 mPreviousPoint = null; 94 mPreviousAngle = (float) Math.PI; 95 mSumSquares = 0.0f; 96 mSum = 0.0f; 97 mCount = 1.0f; 98 mDist = 0.0f; 99 mAnglesCount = mAcceleratingAngles = 0.0f; 100 } 101 addPoint(Point point)102 public void addPoint(Point point) { 103 if (mPreviousPoint != null) { 104 mDist += mPreviousPoint.dist(point); 105 } 106 107 mPreviousPoint = point; 108 Point speedPoint = 109 new Point((float) point.timeOffsetNano / DURATION_SCALE, mDist / LENGTH_SCALE); 110 111 // Checking if the added point is different than the previously added point 112 // Repetitions are being ignored so that proper angles are calculated. 113 if (mLastThreePoints.isEmpty() 114 || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(speedPoint)) { 115 mLastThreePoints.add(speedPoint); 116 if (mLastThreePoints.size() == 4) { 117 mLastThreePoints.remove(0); 118 119 float angle = 120 mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0), mLastThreePoints.get(2)); 121 122 mAnglesCount++; 123 if (angle >= (float) Math.PI - ANGLE_DEVIATION) { 124 mAcceleratingAngles++; 125 } 126 127 float difference = angle - mPreviousAngle; 128 mSum += difference; 129 mSumSquares += difference * difference; 130 mCount += 1.0f; 131 mPreviousAngle = angle; 132 } 133 } 134 } 135 getAnglesVariance()136 public float getAnglesVariance() { 137 return mSumSquares / mCount - (mSum / mCount) * (mSum / mCount); 138 } 139 getAnglesPercentage()140 public float getAnglesPercentage() { 141 if (mAnglesCount == 0.0f) { 142 return 1.0f; 143 } 144 return (mAcceleratingAngles) / mAnglesCount; 145 } 146 } 147 } 148