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