| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.systemui.classifier; |
| |
| import android.os.Build; |
| import android.os.SystemProperties; |
| import android.view.MotionEvent; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * A classifier which for each point from a stroke, it creates a point on plane with coordinates |
| * (timeOffsetNano, distanceCoveredUpToThisPoint) (scaled by DURATION_SCALE and LENGTH_SCALE) |
| * and then it calculates the angle variance of these points like the class |
| * {@link AnglesClassifier} (without splitting it into two parts). The classifier ignores |
| * the last point of a stroke because the UP event comes in with some delay and this ruins the |
| * smoothness of this curve. Additionally, the classifier classifies calculates the percentage of |
| * angles which value is in [PI - ANGLE_DEVIATION, 2* PI) interval. The reason why the classifier |
| * does that is because the speed of a good stroke is most often increases, so most of these angels |
| * should be in this interval. |
| */ |
| public class SpeedAnglesClassifier extends StrokeClassifier { |
| public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.spd_ang", |
| Build.IS_DEBUGGABLE); |
| public static final String TAG = "SPD_ANG"; |
| |
| private HashMap<Stroke, Data> mStrokeMap = new HashMap<>(); |
| |
| public SpeedAnglesClassifier(ClassifierData classifierData) { |
| mClassifierData = classifierData; |
| } |
| |
| @Override |
| public String getTag() { |
| return TAG; |
| } |
| |
| @Override |
| public void onTouchEvent(MotionEvent event) { |
| int action = event.getActionMasked(); |
| |
| if (action == MotionEvent.ACTION_DOWN) { |
| mStrokeMap.clear(); |
| } |
| |
| for (int i = 0; i < event.getPointerCount(); i++) { |
| Stroke stroke = mClassifierData.getStroke(event.getPointerId(i)); |
| |
| if (mStrokeMap.get(stroke) == null) { |
| mStrokeMap.put(stroke, new Data()); |
| } |
| |
| if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL |
| && !(action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) { |
| mStrokeMap.get(stroke).addPoint( |
| stroke.getPoints().get(stroke.getPoints().size() - 1)); |
| } |
| } |
| } |
| |
| @Override |
| public float getFalseTouchEvaluation(int type, Stroke stroke) { |
| Data data = mStrokeMap.get(stroke); |
| return SpeedVarianceEvaluator.evaluate(data.getAnglesVariance()) |
| + SpeedAnglesPercentageEvaluator.evaluate(data.getAnglesPercentage()); |
| } |
| |
| private static class Data { |
| private final float DURATION_SCALE = 1e8f; |
| private final float LENGTH_SCALE = 1.0f; |
| private final float ANGLE_DEVIATION = (float) Math.PI / 10.0f; |
| |
| private List<Point> mLastThreePoints = new ArrayList<>(); |
| private Point mPreviousPoint; |
| private float mPreviousAngle; |
| private float mSumSquares; |
| private float mSum; |
| private float mCount; |
| private float mDist; |
| private float mAnglesCount; |
| private float mAcceleratingAngles; |
| |
| public Data() { |
| mPreviousPoint = null; |
| mPreviousAngle = (float) Math.PI; |
| mSumSquares = 0.0f; |
| mSum = 0.0f; |
| mCount = 1.0f; |
| mDist = 0.0f; |
| mAnglesCount = mAcceleratingAngles = 0.0f; |
| } |
| |
| public void addPoint(Point point) { |
| if (mPreviousPoint != null) { |
| mDist += mPreviousPoint.dist(point); |
| } |
| |
| mPreviousPoint = point; |
| Point speedPoint = new Point((float) point.timeOffsetNano / DURATION_SCALE, |
| mDist / LENGTH_SCALE); |
| |
| // Checking if the added point is different than the previously added point |
| // Repetitions are being ignored so that proper angles are calculated. |
| if (mLastThreePoints.isEmpty() |
| || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(speedPoint)) { |
| mLastThreePoints.add(speedPoint); |
| if (mLastThreePoints.size() == 4) { |
| mLastThreePoints.remove(0); |
| |
| float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0), |
| mLastThreePoints.get(2)); |
| |
| mAnglesCount++; |
| if (angle >= (float) Math.PI - ANGLE_DEVIATION) { |
| mAcceleratingAngles++; |
| } |
| |
| float difference = angle - mPreviousAngle; |
| mSum += difference; |
| mSumSquares += difference * difference; |
| mCount += 1.0; |
| mPreviousAngle = angle; |
| } |
| } |
| } |
| |
| public float getAnglesVariance() { |
| final float v = mSumSquares / mCount - (mSum / mCount) * (mSum / mCount); |
| if (VERBOSE) { |
| FalsingLog.i(TAG, "getAnglesVariance: sum^2=" + mSumSquares |
| + " count=" + mCount + " result=" + v); |
| } |
| return v; |
| } |
| |
| public float getAnglesPercentage() { |
| if (mAnglesCount == 0.0f) { |
| return 1.0f; |
| } |
| final float v = (mAcceleratingAngles) / mAnglesCount; |
| if (VERBOSE) { |
| FalsingLog.i(TAG, "getAnglesPercentage: angles=" + mAcceleratingAngles |
| + " count=" + mAnglesCount + " result=" + v); |
| } |
| return v; |
| } |
| } |
| } |