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
17package com.android.systemui.classifier;
18
19import android.os.Build;
20import android.os.SystemProperties;
21import android.view.MotionEvent;
22
23import java.util.ArrayList;
24import java.util.HashMap;
25import 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 */
38public 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
45    public SpeedAnglesClassifier(ClassifierData classifierData) {
46        mClassifierData = classifierData;
47    }
48
49    @Override
50    public String getTag() {
51        return TAG;
52    }
53
54    @Override
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
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
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
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
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
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}