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