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