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