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 org.chromium.latency.walt;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 
22 import androidx.preference.PreferenceManager;
23 import androidx.annotation.StringRes;
24 
25 import com.github.mikephil.charting.data.Entry;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 
31 /**
32  * Kitchen sink for small utility functions
33  */
34 public class Utils {
median(ArrayList<Double> arrList)35     public static double median(ArrayList<Double> arrList) {
36         ArrayList<Double> lst = new ArrayList<>(arrList);
37         Collections.sort(lst);
38         int len = lst.size();
39         if (len == 0) {
40             return Double.NaN;
41         }
42 
43         if (len % 2 == 1) {
44             return lst.get(len / 2);
45         } else {
46             return 0.5 * (lst.get(len / 2) + lst.get(len / 2 - 1));
47         }
48     }
49 
mean(double[] x)50     public static double mean(double[] x) {
51         double s = 0;
52         for (double v: x) s += v;
53         return s / x.length;
54     }
55 
56     /**
57      * Linear interpolation styled after numpy.interp()
58      * returns values at points x interpolated using xp, yp data points
59      * Both x and xp must be monotonically increasing.
60      */
interp(double[] x, double[] xp, double[] yp)61     public static double[] interp(double[] x, double[] xp, double[] yp) {
62         // assuming that x and xp are already sorted.
63         // go over x and xp as if we are merging them
64         double[] y = new double[x.length];
65         int i = 0;
66         int ip = 0;
67 
68         // skip x points that are outside the data
69         while (i < x.length && x[i] < xp[0]) i++;
70 
71         while (ip < xp.length && i < x.length) {
72             // skip until we see an xp >= current x
73             while (ip < xp.length && xp[ip] < x[i]) ip++;
74             if (ip >= xp.length) break;
75             if (xp[ip] == x[i]) {
76                 y[i] = yp[ip];
77             } else {
78                 double dy = yp[ip] - yp[ip-1];
79                 double dx = xp[ip] - xp[ip-1];
80                 y[i] = yp[ip-1] + dy/dx * (x[i] - xp[ip-1]);
81             }
82             i++;
83         }
84         return y;
85     }
86 
stdev(double[] a)87     public static double stdev(double[] a) {
88         double m = mean(a);
89         double sumsq = 0;
90         for (double v : a) sumsq += (v-m)*(v-m);
91         return Math.sqrt(sumsq / a.length);
92     }
93 
94     /**
95      * Similar to numpy.extract()
96      * returns a shorter array with values taken from x at indices where indicator == value
97      */
extract(int[] indicator, int value, double[] arr)98     public static double[] extract(int[] indicator, int value, double[] arr) {
99         if (arr.length != indicator.length) {
100             throw new IllegalArgumentException("Length of arr and indicator must be the same.");
101         }
102         int newLen = 0;
103         for (int v: indicator) if (v == value) newLen++;
104         double[] newx = new double[newLen];
105 
106         int j = 0;
107         for (int i=0; i<arr.length; i++) {
108             if (indicator[i] == value) {
109                 newx[j] = arr[i];
110                 j++;
111             }
112         }
113         return newx;
114     }
115 
array2string(double[] a, String format)116     public static String array2string(double[] a, String format) {
117         StringBuilder sb = new StringBuilder();
118         sb.append("array([");
119         for (double x: a) {
120             sb.append(String.format(format, x));
121             sb.append(", ");
122         }
123         sb.append("])");
124         return sb.toString();
125     }
126 
argmax(double[] a)127     public static int argmax(double[] a) {
128         int imax = 0;
129         for (int i=1; i<a.length; i++) if (a[i] > a[imax]) imax = i;
130         return imax;
131     }
132 
argmin(double[] a)133     public static int argmin(double[] a) {
134         int imin = 0;
135         for (int i=1; i<a.length; i++) if (a[i] < a[imin]) imin = i;
136         return imin;
137     }
138 
getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift)139     private static double getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift) {
140         double[] T = new double[laserT.length];
141         for (int j=0; j<T.length; j++) {
142             T[j] = laserT[j] + shift;
143         }
144         double [] laserY = Utils.interp(T, touchT, touchY);
145         // TODO: Think about throwing away a percentile of most distanced points for noise reduction
146         return Utils.stdev(laserY);
147     }
148 
149     /**
150      * Simplified Java re-implementation or py/qslog/minimization.py.
151      * This is very specific to the drag latency algorithm.
152      *
153      * tl;dr: Shift laser events by some time delta and see how well they fit on a horizontal line.
154      * Delta that results in the best looking straight line is the latency.
155      */
findBestShift(double[] laserT, double[] touchT, double[] touchY)156     public static double findBestShift(double[] laserT, double[] touchT, double[] touchY) {
157         int steps = 1500;
158         double[] shiftSteps = new double[]{0.1, 0.01};  // milliseconds
159         double[] stddevs = new double[steps];
160         double bestShift = shiftSteps[0]*steps/2;
161         for (final double shiftStep : shiftSteps) {
162             for (int i = 0; i < steps; i++) {
163                 stddevs[i] = getShiftError(laserT, touchT, touchY, bestShift + shiftStep * i - shiftStep * steps / 2);
164             }
165             bestShift = argmin(stddevs) * shiftStep + bestShift - shiftStep * steps / 2;
166         }
167         return bestShift;
168     }
169 
char2byte(char c)170     static byte[] char2byte(char c) {
171         return new byte[]{(byte) c};
172     }
173 
getIntPreference(Context context, @StringRes int keyId, int defaultValue)174     static int getIntPreference(Context context, @StringRes int keyId, int defaultValue) {
175         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
176         return preferences.getInt(context.getString(keyId), defaultValue);
177     }
178 
getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue)179     static boolean getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue) {
180         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
181         return preferences.getBoolean(context.getString(keyId), defaultValue);
182     }
183 
getStringPreference(Context context, @StringRes int keyId, String defaultValue)184     static String getStringPreference(Context context, @StringRes int keyId, String defaultValue) {
185         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
186         return preferences.getString(context.getString(keyId), defaultValue);
187     }
188 
min(List<Entry> entries)189     static float min(List<Entry> entries) {
190         float min = Float.MAX_VALUE;
191         for (Entry e : entries) {
192             min = Math.min(min, e.getY());
193         }
194         return min;
195     }
196 
max(List<Entry> entries)197     static float max(List<Entry> entries) {
198         float max = Float.MIN_VALUE;
199         for (Entry e : entries) {
200             max = Math.max(max, e.getY());
201         }
202         return max;
203     }
204 
mean(List<Entry> entries)205     static float mean(List<Entry> entries) {
206         float mean = 0;
207         for (Entry e : entries) {
208             mean += e.getY()/entries.size();
209         }
210         return mean;
211     }
212 
213     public enum ListenerState {
214         RUNNING,
215         STARTING,
216         STOPPED,
217         STOPPING
218     }
219 
220 }
221