1 package org.robolectric.shadows;
2 
3 import static org.robolectric.util.reflector.Reflector.reflector;
4 
5 import android.os.Parcel;
6 import android.os.health.HealthStats;
7 import android.os.health.TimerStat;
8 import android.util.ArrayMap;
9 import com.google.common.annotations.VisibleForTesting;
10 import com.google.errorprone.annotations.CanIgnoreReturnValue;
11 import java.util.Arrays;
12 import java.util.HashMap;
13 import java.util.Set;
14 import org.robolectric.util.reflector.Accessor;
15 import org.robolectric.util.reflector.ForType;
16 
17 /** Test helper class to build {@link HealthStats} */
18 final class HealthStatsBuilder {
19 
20   // Header fields
21   private String dataType = null;
22 
23   // TimerStat fields
24   private final HashMap<Integer, TimerStat> timerMap = new HashMap<>();
25 
26   // Measurement fields
27   private final HashMap<Integer, Long> measurementMap = new HashMap<>();
28 
29   // Stats fields
30   private final HashMap<Integer, ArrayMap<String, HealthStats>> statsMap = new HashMap<>();
31 
32   // Timers fields
33   private final HashMap<Integer, ArrayMap<String, TimerStat>> timersMap = new HashMap<>();
34 
35   // Measurements fields
36   private final HashMap<Integer, ArrayMap<String, Long>> measurementsMap = new HashMap<>();
37 
newBuilder()38   public static HealthStatsBuilder newBuilder() {
39     return new HealthStatsBuilder();
40   }
41 
42   /** Sets the DataType. Defaults to null. */
43   @CanIgnoreReturnValue
setDataType(String dataType)44   public HealthStatsBuilder setDataType(String dataType) {
45     this.dataType = dataType;
46     return this;
47   }
48 
49   /**
50    * Adds a TimerStat for the given key. If the same key is used multiple times, the last provided
51    * TimerStat is used.
52    */
53   @CanIgnoreReturnValue
addTimerStat(int key, TimerStat value)54   public HealthStatsBuilder addTimerStat(int key, TimerStat value) {
55     timerMap.put(key, value);
56     return this;
57   }
58 
59   /**
60    * Adds a measurement for the given key. If the same key is used multiple times, the last provided
61    * measurement is used.
62    */
63   @CanIgnoreReturnValue
addMeasurement(int key, long value)64   public HealthStatsBuilder addMeasurement(int key, long value) {
65     measurementMap.put(key, value);
66     return this;
67   }
68 
69   /**
70    * Adds a map of HealthStats for the given key. If the same key is used multiple times, the last
71    * provided map is used.
72    */
73   @CanIgnoreReturnValue
addStats(int key, ArrayMap<String, HealthStats> value)74   public HealthStatsBuilder addStats(int key, ArrayMap<String, HealthStats> value) {
75     statsMap.put(key, new ArrayMap<String, HealthStats>(value));
76     return this;
77   }
78 
79   /**
80    * Adds a map of TimerStats for the given key. If the same key is used multiple times, the last
81    * provided map is used.
82    */
83   @CanIgnoreReturnValue
addTimers(int key, ArrayMap<String, TimerStat> value)84   public HealthStatsBuilder addTimers(int key, ArrayMap<String, TimerStat> value) {
85     timersMap.put(key, new ArrayMap<String, TimerStat>(value));
86     return this;
87   }
88 
89   /**
90    * Adds a map of measurements for the given key. If the same key is used multiple times, the last
91    * provided map is used.
92    */
93   @CanIgnoreReturnValue
addMeasurements(int key, ArrayMap<String, Long> value)94   public HealthStatsBuilder addMeasurements(int key, ArrayMap<String, Long> value) {
95     measurementsMap.put(key, new ArrayMap<String, Long>(value));
96     return this;
97   }
98 
99   // HealthStats has internal fields that are generic arrays, so this is unavoidable.
100   @SuppressWarnings("unchecked")
build()101   public HealthStats build() {
102     // HealthStats' default constructor throws an exception
103     HealthStats result = new HealthStats(Parcel.obtain());
104 
105     reflector(HealthStatsReflector.class, result).setDataType(dataType);
106 
107     int[] mTimerKeys = toSortedIntArray(timerMap.keySet());
108     reflector(HealthStatsReflector.class, result).setTimerKeys(mTimerKeys);
109     int[] mTimerCounts = new int[mTimerKeys.length];
110     long[] mTimerTimes = new long[mTimerKeys.length];
111     for (int i = 0; i < mTimerKeys.length; i++) {
112       TimerStat timerStat = timerMap.get(mTimerKeys[i]);
113       mTimerCounts[i] = timerStat.getCount();
114       mTimerTimes[i] = timerStat.getTime();
115     }
116     reflector(HealthStatsReflector.class, result).setTimerCounts(mTimerCounts);
117     reflector(HealthStatsReflector.class, result).setTimerTimes(mTimerTimes);
118 
119     int[] mMeasurementKeys = toSortedIntArray(measurementMap.keySet());
120     reflector(HealthStatsReflector.class, result).setMeasurementKeys(mMeasurementKeys);
121     long[] mMeasurementValues = new long[mMeasurementKeys.length];
122     for (int i = 0; i < mMeasurementKeys.length; i++) {
123       long measurementValue = measurementMap.get(mMeasurementKeys[i]);
124       mMeasurementValues[i] = measurementValue;
125     }
126     reflector(HealthStatsReflector.class, result).setMeasurementValues(mMeasurementValues);
127 
128     int[] mStatsKeys = toSortedIntArray(statsMap.keySet());
129     reflector(HealthStatsReflector.class, result).setStatsKeys(mStatsKeys);
130     ArrayMap<String, HealthStats>[] mStatsValues =
131         (ArrayMap<String, HealthStats>[]) new ArrayMap<?, ?>[mStatsKeys.length];
132     for (int i = 0; i < mStatsKeys.length; i++) {
133       ArrayMap<String, HealthStats> stats = statsMap.get(mStatsKeys[i]);
134       mStatsValues[i] = stats;
135     }
136     reflector(HealthStatsReflector.class, result).setStatsValues(mStatsValues);
137 
138     int[] mTimersKeys = toSortedIntArray(timersMap.keySet());
139     reflector(HealthStatsReflector.class, result).setTimersKeys(mTimersKeys);
140     ArrayMap<String, TimerStat>[] mTimersValues =
141         (ArrayMap<String, TimerStat>[]) new ArrayMap<?, ?>[mTimersKeys.length];
142     for (int i = 0; i < mTimersKeys.length; i++) {
143       ArrayMap<String, TimerStat> timers = timersMap.get(mTimersKeys[i]);
144       mTimersValues[i] = timers;
145     }
146     reflector(HealthStatsReflector.class, result).setTimersValues(mTimersValues);
147 
148     int[] mMeasurementsKeys = toSortedIntArray(measurementsMap.keySet());
149     reflector(HealthStatsReflector.class, result).setMeasurementsKeys(mMeasurementsKeys);
150     ArrayMap<String, Long>[] mMeasurementsValues =
151         (ArrayMap<String, Long>[]) new ArrayMap<?, ?>[mMeasurementsKeys.length];
152     for (int i = 0; i < mMeasurementsKeys.length; i++) {
153       ArrayMap<String, Long> measurements = measurementsMap.get(mMeasurementsKeys[i]);
154       mMeasurementsValues[i] = measurements;
155     }
156     reflector(HealthStatsReflector.class, result).setMeasurementsValues(mMeasurementsValues);
157 
158     return result;
159   }
160 
HealthStatsBuilder()161   private HealthStatsBuilder() {}
162 
163   @ForType(HealthStats.class)
164   private interface HealthStatsReflector {
165 
166     @Accessor("mDataType")
setDataType(String dataType)167     void setDataType(String dataType);
168 
169     @Accessor("mTimerKeys")
setTimerKeys(int[] timerKeys)170     void setTimerKeys(int[] timerKeys);
171 
172     @Accessor("mTimerCounts")
setTimerCounts(int[] timerCounts)173     void setTimerCounts(int[] timerCounts);
174 
175     @Accessor("mTimerTimes")
setTimerTimes(long[] timerTimes)176     void setTimerTimes(long[] timerTimes);
177 
178     @Accessor("mMeasurementKeys")
setMeasurementKeys(int[] measurementKeys)179     void setMeasurementKeys(int[] measurementKeys);
180 
181     @Accessor("mMeasurementValues")
setMeasurementValues(long[] measurementValues)182     void setMeasurementValues(long[] measurementValues);
183 
184     @Accessor("mStatsKeys")
setStatsKeys(int[] statsKeys)185     void setStatsKeys(int[] statsKeys);
186 
187     @Accessor("mStatsValues")
setStatsValues(ArrayMap<String, HealthStats>[] statsValues)188     void setStatsValues(ArrayMap<String, HealthStats>[] statsValues);
189 
190     @Accessor("mTimersKeys")
setTimersKeys(int[] timersKeys)191     void setTimersKeys(int[] timersKeys);
192 
193     @Accessor("mTimersValues")
setTimersValues(ArrayMap<String, TimerStat>[] timersValues)194     void setTimersValues(ArrayMap<String, TimerStat>[] timersValues);
195 
196     @Accessor("mMeasurementsKeys")
setMeasurementsKeys(int[] measurementsKeys)197     void setMeasurementsKeys(int[] measurementsKeys);
198 
199     @Accessor("mMeasurementsValues")
setMeasurementsValues(ArrayMap<String, Long>[] measurementsValues)200     void setMeasurementsValues(ArrayMap<String, Long>[] measurementsValues);
201   }
202 
203   @VisibleForTesting
toSortedIntArray(Set<Integer> set)204   static final int[] toSortedIntArray(Set<Integer> set) {
205     int[] result = new int[set.size()];
206     Object[] inputObjArray = set.toArray();
207     for (int i = 0; i < inputObjArray.length; i++) {
208       result[i] = (int) inputObjArray[i];
209     }
210     // mFooKeys fields of HealthStats are used in Arrays.binarySearch, so they have to be sorted.
211     Arrays.sort(result);
212     return result;
213   }
214 }
215