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.SystemClock;
20
21import java.util.ArrayList;
22
23/**
24 * Holds the evaluations for ended strokes and gestures. These values are decreased through time.
25 */
26public class HistoryEvaluator {
27    private static final float INTERVAL = 50.0f;
28    private static final float HISTORY_FACTOR = 0.9f;
29    private static final float EPSILON = 1e-5f;
30
31    private final ArrayList<Data> mStrokes = new ArrayList<>();
32    private final ArrayList<Data> mGestureWeights = new ArrayList<>();
33    private long mLastUpdate;
34
35    public HistoryEvaluator() {
36        mLastUpdate = SystemClock.elapsedRealtime();
37    }
38
39    public void addStroke(float evaluation) {
40        decayValue();
41        mStrokes.add(new Data(evaluation));
42    }
43
44    public void addGesture(float evaluation) {
45        decayValue();
46        mGestureWeights.add(new Data(evaluation));
47    }
48
49    /**
50     * Calculates the weighted average of strokes and adds to it the weighted average of gestures
51     */
52    public float getEvaluation() {
53        return weightedAverage(mStrokes) + weightedAverage(mGestureWeights);
54    }
55
56    private float weightedAverage(ArrayList<Data> list) {
57        float sumValue = 0.0f;
58        float sumWeight = 0.0f;
59        int size = list.size();
60        for (int i = 0; i < size; i++) {
61            Data data = list.get(i);
62            sumValue += data.evaluation * data.weight;
63            sumWeight += data.weight;
64        }
65
66        if (sumWeight == 0.0f) {
67            return 0.0f;
68        }
69
70        return sumValue / sumWeight;
71    }
72
73    private void decayValue() {
74        long time = SystemClock.elapsedRealtime();
75
76        if (time <= mLastUpdate) {
77            return;
78        }
79
80        // All weights are multiplied by HISTORY_FACTOR after each INTERVAL milliseconds.
81        float factor = (float) Math.pow(HISTORY_FACTOR, (time - mLastUpdate) / INTERVAL);
82
83        decayValue(mStrokes, factor);
84        decayValue(mGestureWeights, factor);
85        mLastUpdate = time;
86    }
87
88    private void decayValue(ArrayList<Data> list, float factor) {
89        int size = list.size();
90        for (int i = 0; i < size; i++) {
91            list.get(i).weight *= factor;
92        }
93
94        // Removing evaluations with such small weights that they do not matter anymore
95        while (!list.isEmpty() && isZero(list.get(0).weight)) {
96            list.remove(0);
97        }
98    }
99
100    private boolean isZero(float x) {
101        return x <= EPSILON && x >= -EPSILON;
102    }
103
104    /**
105     * For each stroke it holds its initial value and the current weight. Initially the
106     * weight is set to 1.0
107     */
108    private static class Data {
109        public float evaluation;
110        public float weight;
111
112        public Data(float evaluation) {
113            this.evaluation = evaluation;
114            weight = 1.0f;
115        }
116    }
117}
118