1/*
2 * Copyright (C) 2016 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 android.os.health;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.util.ArrayMap;
22
23import java.util.Map;
24
25/**
26 * Class to write the health stats data into a parcel, so it can then be
27 * retrieved via a {@link HealthStats} object.
28 *
29 * There is an attempt to keep this class as low overhead as possible, for
30 * example storing an int[] and a long[] instead of a TimerStat[].
31 *
32 * @hide
33 */
34public class HealthStatsWriter {
35    private final HealthKeys.Constants mConstants;
36
37    // TimerStat fields
38    private final boolean[] mTimerFields;
39    private final int[] mTimerCounts;
40    private final long[] mTimerTimes;
41
42    // Measurement fields
43    private final boolean[] mMeasurementFields;
44    private final long[] mMeasurementValues;
45
46    // Stats fields
47    private final ArrayMap<String,HealthStatsWriter>[] mStatsValues;
48
49    // Timers fields
50    private final ArrayMap<String,TimerStat>[] mTimersValues;
51
52    // Measurements fields
53    private final ArrayMap<String,Long>[] mMeasurementsValues;
54
55    /**
56     * Construct a HealthStatsWriter object with the given constants.
57     *
58     * The "getDataType()" of the resulting HealthStats object will be the
59     * short name of the java class that the Constants object was initalized
60     * with.
61     */
62    public HealthStatsWriter(HealthKeys.Constants constants) {
63        mConstants = constants;
64
65        // TimerStat
66        final int timerCount = constants.getSize(HealthKeys.TYPE_TIMER);
67        mTimerFields = new boolean[timerCount];
68        mTimerCounts = new int[timerCount];
69        mTimerTimes = new long[timerCount];
70
71        // Measurement
72        final int measurementCount = constants.getSize(HealthKeys.TYPE_MEASUREMENT);
73        mMeasurementFields = new boolean[measurementCount];
74        mMeasurementValues = new long[measurementCount];
75
76        // Stats
77        final int statsCount = constants.getSize(HealthKeys.TYPE_STATS);
78        mStatsValues = new ArrayMap[statsCount];
79
80        // Timers
81        final int timersCount = constants.getSize(HealthKeys.TYPE_TIMERS);
82        mTimersValues = new ArrayMap[timersCount];
83
84        // Measurements
85        final int measurementsCount = constants.getSize(HealthKeys.TYPE_MEASUREMENTS);
86        mMeasurementsValues = new ArrayMap[measurementsCount];
87    }
88
89    /**
90     * Add a timer for the given key.
91     */
92    public void addTimer(int timerId, int count, long time) {
93        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMER, timerId);
94
95        mTimerFields[index] = true;
96        mTimerCounts[index] = count;
97        mTimerTimes[index] = time;
98    }
99
100    /**
101     * Add a measurement for the given key.
102     */
103    public void addMeasurement(int measurementId, long value) {
104        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENT, measurementId);
105
106        mMeasurementFields[index] = true;
107        mMeasurementValues[index] = value;
108    }
109
110    /**
111     * Add a recursive HealthStats object for the given key and string name. The value
112     * is stored as a HealthStatsWriter until this object is written to a parcel, so
113     * don't attempt to reuse the HealthStatsWriter.
114     *
115     * The value field should not be null.
116     */
117    public void addStats(int key, String name, HealthStatsWriter value) {
118        final int index = mConstants.getIndex(HealthKeys.TYPE_STATS, key);
119
120        ArrayMap<String,HealthStatsWriter> map = mStatsValues[index];
121        if (map == null) {
122            map = mStatsValues[index] = new ArrayMap<String,HealthStatsWriter>(1);
123        }
124        map.put(name, value);
125    }
126
127    /**
128     * Add a TimerStat for the given key and string name.
129     *
130     * The value field should not be null.
131     */
132    public void addTimers(int key, String name, TimerStat value) {
133        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMERS, key);
134
135        ArrayMap<String,TimerStat> map = mTimersValues[index];
136        if (map == null) {
137            map = mTimersValues[index] = new ArrayMap<String,TimerStat>(1);
138        }
139        map.put(name, value);
140    }
141
142    /**
143     * Add a measurement for the given key and string name.
144     */
145    public void addMeasurements(int key, String name, long value) {
146        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENTS, key);
147
148        ArrayMap<String,Long> map = mMeasurementsValues[index];
149        if (map == null) {
150            map = mMeasurementsValues[index] = new ArrayMap<String,Long>(1);
151        }
152        map.put(name, value);
153    }
154
155    /**
156     * Flattens the data in this HealthStatsWriter to the Parcel format
157     * that can be unparceled into a HealthStat.
158     * @more
159     * (Called flattenToParcel because this HealthStatsWriter itself is
160     * not parcelable and we don't flatten all the business about the
161     * HealthKeys.Constants, only the values that were actually supplied)
162     */
163    public void flattenToParcel(Parcel out) {
164        int[] keys;
165
166        // Header fields
167        out.writeString(mConstants.getDataType());
168
169        // TimerStat fields
170        out.writeInt(countBooleanArray(mTimerFields));
171        keys = mConstants.getKeys(HealthKeys.TYPE_TIMER);
172        for (int i=0; i<keys.length; i++) {
173            if (mTimerFields[i]) {
174                out.writeInt(keys[i]);
175                out.writeInt(mTimerCounts[i]);
176                out.writeLong(mTimerTimes[i]);
177            }
178        }
179
180        // Measurement fields
181        out.writeInt(countBooleanArray(mMeasurementFields));
182        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENT);
183        for (int i=0; i<keys.length; i++) {
184            if (mMeasurementFields[i]) {
185                out.writeInt(keys[i]);
186                out.writeLong(mMeasurementValues[i]);
187            }
188        }
189
190        // Stats
191        out.writeInt(countObjectArray(mStatsValues));
192        keys = mConstants.getKeys(HealthKeys.TYPE_STATS);
193        for (int i=0; i<keys.length; i++) {
194            if (mStatsValues[i] != null) {
195                out.writeInt(keys[i]);
196                writeHealthStatsWriterMap(out, mStatsValues[i]);
197            }
198        }
199
200        // Timers
201        out.writeInt(countObjectArray(mTimersValues));
202        keys = mConstants.getKeys(HealthKeys.TYPE_TIMERS);
203        for (int i=0; i<keys.length; i++) {
204            if (mTimersValues[i] != null) {
205                out.writeInt(keys[i]);
206                writeParcelableMap(out, mTimersValues[i]);
207            }
208        }
209
210        // Measurements
211        out.writeInt(countObjectArray(mMeasurementsValues));
212        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENTS);
213        for (int i=0; i<keys.length; i++) {
214            if (mMeasurementsValues[i] != null) {
215                out.writeInt(keys[i]);
216                writeLongsMap(out, mMeasurementsValues[i]);
217            }
218        }
219    }
220
221    /**
222     * Count how many of the fields have been set.
223     */
224    private static int countBooleanArray(boolean[] fields) {
225        int count = 0;
226        final int N = fields.length;
227        for (int i=0; i<N; i++) {
228            if (fields[i]) {
229                count++;
230            }
231        }
232        return count;
233    }
234
235    /**
236     * Count how many of the fields have been set.
237     */
238    private static <T extends Object> int countObjectArray(T[] fields) {
239        int count = 0;
240        final int N = fields.length;
241        for (int i=0; i<N; i++) {
242            if (fields[i] != null) {
243                count++;
244            }
245        }
246        return count;
247    }
248
249    /**
250     * Write a map of String to HealthStatsWriter to the Parcel.
251     */
252    private static void writeHealthStatsWriterMap(Parcel out,
253            ArrayMap<String,HealthStatsWriter> map) {
254        final int N = map.size();
255        out.writeInt(N);
256        for (int i=0; i<N; i++) {
257            out.writeString(map.keyAt(i));
258            map.valueAt(i).flattenToParcel(out);
259        }
260    }
261
262    /**
263     * Write a map of String to Parcelables to the Parcel.
264     */
265    private static <T extends Parcelable> void writeParcelableMap(Parcel out,
266            ArrayMap<String,T> map) {
267        final int N = map.size();
268        out.writeInt(N);
269        for (int i=0; i<N; i++) {
270            out.writeString(map.keyAt(i));
271            map.valueAt(i).writeToParcel(out, 0);
272        }
273    }
274
275    /**
276     * Write a map of String to Longs to the Parcel.
277     */
278    private static void writeLongsMap(Parcel out, ArrayMap<String,Long> map) {
279        final int N = map.size();
280        out.writeInt(N);
281        for (int i=0; i<N; i++) {
282            out.writeString(map.keyAt(i));
283            out.writeLong(map.valueAt(i));
284        }
285    }
286}
287
288
289