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