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