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