1 /*
2  * Copyright (C) 2020 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 com.android.server.power.stats;
18 
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.hardware.power.stats.EnergyConsumer;
23 import android.hardware.power.stats.EnergyConsumerAttribution;
24 import android.hardware.power.stats.EnergyConsumerResult;
25 import android.hardware.power.stats.EnergyConsumerType;
26 import android.os.BatteryStats.EnergyConsumerDetails;
27 import android.util.Slog;
28 import android.util.SparseArray;
29 import android.util.SparseIntArray;
30 import android.util.SparseLongArray;
31 
32 import java.io.PrintWriter;
33 
34 /**
35  * Keeps snapshots of data from previously pulled EnergyConsumerResults.
36  */
37 public class EnergyConsumerSnapshot {
38     private static final String TAG = "EnergyConsumerSnapshot";
39 
40     private static final int MILLIVOLTS_PER_VOLT = 1000;
41 
42     public static final long UNAVAILABLE = android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
43 
44     /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
45     private final SparseArray<EnergyConsumer> mEnergyConsumers;
46 
47     /** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */
48     private final int mNumCpuClusterOrdinals;
49 
50     /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */
51     private final int mNumDisplayOrdinals;
52 
53     /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
54     private final int mNumOtherOrdinals;
55 
56     /**
57      * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
58      * each {@link EnergyConsumer} was updated.
59      *
60      * Note that the snapshots for different ids may have been taken at different times.
61      * Note that energies for all existing ids are stored here, including each ordinal of type
62      * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
63      *
64      * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
65      */
66     private final SparseLongArray mEnergyConsumerSnapshots;
67 
68     /**
69      * Voltage snapshots, mapping {@link EnergyConsumer#id} to voltage (mV) from the last time
70      * each {@link EnergyConsumer} was updated.
71      *
72      * see {@link #mEnergyConsumerSnapshots}.
73      */
74     private final SparseIntArray mVoltageSnapshots;
75 
76     /**
77      * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
78      * {@link EnergyConsumerType#OTHER} was updated.
79      * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
80      * uid to an energy (UJ). That is,
81      * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
82      *
83      * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
84      * If an id is present but a uid is not present, that uid's energy is 0.
85      */
86     private final SparseArray<SparseLongArray> mAttributionSnapshots;
87 
88     private EnergyConsumerDetails mEnergyConsumerDetails;
89 
90     /**
91      * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
92      * exist and what their details are.
93      */
EnergyConsumerSnapshot(@onNull SparseArray<EnergyConsumer> idToConsumerMap)94     EnergyConsumerSnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
95         mEnergyConsumers = idToConsumerMap;
96         mEnergyConsumerSnapshots = new SparseLongArray(mEnergyConsumers.size());
97         mVoltageSnapshots = new SparseIntArray(mEnergyConsumers.size());
98 
99         mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER,
100                 idToConsumerMap);
101         mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap);
102         mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap);
103         mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
104     }
105 
106     /** Class for returning the relevant data calculated from the energy consumer delta */
107     static class EnergyConsumerDeltaData {
108         /** The chargeUC for {@link EnergyConsumerType#BLUETOOTH}. */
109         public long bluetoothChargeUC = UNAVAILABLE;
110 
111         /** The chargeUC for {@link EnergyConsumerType#CPU_CLUSTER}s. */
112         public long[] cpuClusterChargeUC = null;
113 
114         /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
115         public long[] displayChargeUC = null;
116 
117         /** The chargeUC for {@link EnergyConsumerType#GNSS}. */
118         public long gnssChargeUC = UNAVAILABLE;
119 
120         /** The chargeUC for {@link EnergyConsumerType#MOBILE_RADIO}. */
121         public long mobileRadioChargeUC = UNAVAILABLE;
122 
123         /** The chargeUC for {@link EnergyConsumerType#WIFI}. */
124         public long wifiChargeUC = UNAVAILABLE;
125 
126         /** The chargeUC for {@link EnergyConsumerType#CAMERA}. */
127         public long cameraChargeUC = UNAVAILABLE;
128 
129         /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */
130         public @Nullable long[] otherTotalChargeUC = null;
131 
132         /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->chargeUC} maps. */
133         public @Nullable SparseLongArray[] otherUidChargesUC = null;
134 
isEmpty()135         boolean isEmpty() {
136             return bluetoothChargeUC <= 0
137                     && isEmpty(cpuClusterChargeUC)
138                     && isEmpty(displayChargeUC)
139                     && gnssChargeUC <= 0
140                     && mobileRadioChargeUC <= 0
141                     && wifiChargeUC <= 0
142                     && isEmpty(otherTotalChargeUC);
143         }
144 
isEmpty(long[] values)145         private boolean isEmpty(long[] values) {
146             if (values == null) {
147                 return true;
148             }
149             for (long value: values) {
150                 if (value > 0) {
151                     return false;
152                 }
153             }
154             return true;
155         }
156     }
157 
158     /**
159      * Update with the freshly retrieved energy consumers and return the difference (delta)
160      * between the previously stored values and the passed-in values.
161      *
162      * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
163      *             Consumers that are not present are ignored (they are *not* treated as 0).
164      * @param voltageMV current voltage.
165      *
166      * @return an EnergyConsumerDeltaData, containing maps from the updated consumers to
167      *         their corresponding charge deltas.
168      *         Fields with no interesting data (consumers not present in ecrs or with no energy
169      *         difference) will generally be left as their default values.
170      *         otherTotalChargeUC and otherUidChargesUC are always either both null or both of
171      *         length {@link #getOtherOrdinalNames().length}.
172      *         Returns null, if ecrs is null or empty.
173      */
174     @Nullable
updateAndGetDelta(EnergyConsumerResult[] ecrs, int voltageMV)175     public EnergyConsumerDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs, int voltageMV) {
176         if (ecrs == null || ecrs.length == 0) {
177             return null;
178         }
179         if (voltageMV <= 0) {
180             Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMV
181                     + " mV) when taking energy consumer snapshot");
182             // TODO (b/181685156): consider adding the nominal voltage to power profile and
183             //  falling back to it if measured voltage is unavailable.
184             return null;
185         }
186         final EnergyConsumerDeltaData output = new EnergyConsumerDeltaData();
187 
188         for (final EnergyConsumerResult ecr : ecrs) {
189             // Extract the new energy data for the current consumer.
190             final int consumerId = ecr.id;
191             final long newEnergyUJ = ecr.energyUWs;
192             final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
193 
194             // Look up the static information about this consumer.
195             final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
196             if (consumer == null) {
197                 Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
198                 continue;
199             }
200             final int type = consumer.type;
201             final int ordinal = consumer.ordinal;
202 
203             // Look up, and update, the old energy and voltage information about this consumer.
204             final long oldEnergyUJ = mEnergyConsumerSnapshots.get(consumerId, UNAVAILABLE);
205             final int oldVoltageMV = mVoltageSnapshots.get(consumerId);
206             mEnergyConsumerSnapshots.put(consumerId, newEnergyUJ);
207             mVoltageSnapshots.put(consumerId, voltageMV);
208 
209             final int avgVoltageMV = (oldVoltageMV + voltageMV + 1) / 2;
210             final SparseLongArray otherUidCharges =
211                     updateAndGetDeltaForTypeOther(consumer, newAttributions, avgVoltageMV);
212             // Everything is fully done being updated. We now calculate the delta for returning.
213 
214             // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
215             // there's no attribution either. Technically that isn't enforced at the HAL, but we
216             // can't really trust data like that anyway.
217 
218             if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
219             if (newEnergyUJ == oldEnergyUJ) continue;
220 
221             final long deltaUJ = newEnergyUJ - oldEnergyUJ;
222             if (deltaUJ < 0 || oldVoltageMV <= 0) {
223                 Slog.e(TAG, "Bad data! EnergyConsumer " + consumer.name
224                         + ": new energy (" + newEnergyUJ + ") < old energy (" + oldEnergyUJ
225                         + "), new voltage (" + voltageMV + "), old voltage (" + oldVoltageMV
226                         + "). Skipping. ");
227                 continue;
228             }
229 
230             final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV);
231             switch (type) {
232                 case EnergyConsumerType.BLUETOOTH:
233                     output.bluetoothChargeUC = deltaChargeUC;
234                     break;
235 
236                 case EnergyConsumerType.CPU_CLUSTER:
237                     if (output.cpuClusterChargeUC == null) {
238                         output.cpuClusterChargeUC = new long[mNumCpuClusterOrdinals];
239                     }
240                     output.cpuClusterChargeUC[ordinal] = deltaChargeUC;
241                     break;
242 
243                 case EnergyConsumerType.DISPLAY:
244                     if (output.displayChargeUC == null) {
245                         output.displayChargeUC = new long[mNumDisplayOrdinals];
246                     }
247                     output.displayChargeUC[ordinal]  = deltaChargeUC;
248                     break;
249 
250                 case EnergyConsumerType.GNSS:
251                     output.gnssChargeUC = deltaChargeUC;
252                     break;
253 
254                 case EnergyConsumerType.MOBILE_RADIO:
255                     output.mobileRadioChargeUC = deltaChargeUC;
256                     break;
257 
258                 case EnergyConsumerType.WIFI:
259                     output.wifiChargeUC = deltaChargeUC;
260                     break;
261 
262                 case EnergyConsumerType.CAMERA:
263                     output.cameraChargeUC = deltaChargeUC;
264                     break;
265 
266                 case EnergyConsumerType.OTHER:
267                     if (output.otherTotalChargeUC == null) {
268                         output.otherTotalChargeUC = new long[mNumOtherOrdinals];
269                         output.otherUidChargesUC = new SparseLongArray[mNumOtherOrdinals];
270                     }
271                     output.otherTotalChargeUC[ordinal] = deltaChargeUC;
272                     output.otherUidChargesUC[ordinal] = otherUidCharges;
273                     break;
274 
275                 default:
276                     Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
277 
278             }
279         }
280         return output;
281     }
282 
283     /**
284      * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
285      * {@link #mAttributionSnapshots} with freshly retrieved energy consumers (per uid) and returns
286      * the charge consumed (in microcoulombs) between the previously stored values and the passed-in
287      * values.
288      *
289      * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
290      * @param newAttributions Record of uids and their new energyUJ values.
291      *                        Any uid not present is treated as having energy 0.
292      *                        If null or empty, all uids are treated as having energy 0.
293      * @param avgVoltageMV The average voltage since the last snapshot.
294      * @return A map (in the sense of {@link EnergyConsumerDeltaData#otherUidChargesUC} for this
295      *         consumer) of uid -> chargeDelta, with all uids that have a non-zero chargeDelta.
296      *         Returns null if no delta available to calculate.
297      */
updateAndGetDeltaForTypeOther( @onNull EnergyConsumer consumerInfo, @Nullable EnergyConsumerAttribution[] newAttributions, int avgVoltageMV)298     private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
299             @NonNull EnergyConsumer consumerInfo,
300             @Nullable EnergyConsumerAttribution[] newAttributions, int avgVoltageMV) {
301 
302         if (consumerInfo.type != EnergyConsumerType.OTHER) {
303             return null;
304         }
305         if (newAttributions == null) {
306             // Treat null as empty (i.e. all uids have 0 energy).
307             newAttributions = new EnergyConsumerAttribution[0];
308         }
309 
310         // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
311         SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
312 
313         // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
314         if (uidOldEnergyMap == null) {
315             uidOldEnergyMap = new SparseLongArray(newAttributions.length);
316             mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
317             for (EnergyConsumerAttribution newAttribution : newAttributions) {
318                 uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
319             }
320             return null;
321         }
322 
323         // Map uid -> chargeDelta. No initial capacity since many deltas might be 0.
324         final SparseLongArray uidChargeDeltas = new SparseLongArray();
325 
326         for (EnergyConsumerAttribution newAttribution : newAttributions) {
327             final int uid = newAttribution.uid;
328             final long newEnergyUJ = newAttribution.energyUWs;
329             // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
330             final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
331             uidOldEnergyMap.put(uid, newEnergyUJ);
332 
333             // Everything is fully done being updated. We now calculate the delta for returning.
334             if (oldEnergyUJ < 0) continue;
335             if (newEnergyUJ == oldEnergyUJ) continue;
336             final long deltaUJ = newEnergyUJ - oldEnergyUJ;
337             if (deltaUJ < 0 || avgVoltageMV <= 0) {
338                 Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
339                         + ") but old energy (" + oldEnergyUJ + "). Average voltage (" + avgVoltageMV
340                         + ")Skipping. ");
341                 continue;
342             }
343 
344             final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV);
345             uidChargeDeltas.put(uid, deltaChargeUC);
346         }
347         return uidChargeDeltas;
348     }
349 
350     /** Dump debug data. */
dump(PrintWriter pw)351     public void dump(PrintWriter pw) {
352         pw.println("Energy consumer snapshot");
353         pw.println("List of EnergyConsumers:");
354         for (int i = 0; i < mEnergyConsumers.size(); i++) {
355             final int id = mEnergyConsumers.keyAt(i);
356             final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
357             pw.println(String.format("    Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
358                     consumer.id, consumer.ordinal, consumer.type, consumer.name));
359         }
360         pw.println("Map of consumerIds to energy (in microjoules):");
361         for (int i = 0; i < mEnergyConsumerSnapshots.size(); i++) {
362             final int id = mEnergyConsumerSnapshots.keyAt(i);
363             final long energyUJ = mEnergyConsumerSnapshots.valueAt(i);
364             final long voltageMV = mVoltageSnapshots.valueAt(i);
365             pw.println(String.format("    Consumer %d has energy %d uJ at %d mV", id, energyUJ,
366                     voltageMV));
367         }
368         pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
369         pw.println("    " + mAttributionSnapshots);
370         pw.println();
371     }
372 
373     /**
374      * Returns the names of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the names of
375      * custom energy buckets supported by the device.
376      */
getOtherOrdinalNames()377     public String[] getOtherOrdinalNames() {
378         final String[] names = new String[mNumOtherOrdinals];
379         int consumerIndex = 0;
380         final int size = mEnergyConsumers.size();
381         for (int idx = 0; idx < size; idx++) {
382             final EnergyConsumer consumer = mEnergyConsumers.valueAt(idx);
383             if (consumer.type == (int) EnergyConsumerType.OTHER) {
384                 names[consumerIndex++] = sanitizeCustomBucketName(consumer.name);
385             }
386         }
387         return names;
388     }
389 
sanitizeCustomBucketName(String bucketName)390     private String sanitizeCustomBucketName(String bucketName) {
391         if (bucketName == null) {
392             return "";
393         }
394         StringBuilder sb = new StringBuilder(bucketName.length());
395         for (char c : bucketName.toCharArray()) {
396             if (Character.isWhitespace(c)) {
397                 sb.append(' ');
398             } else if (Character.isISOControl(c)) {
399                 sb.append('_');
400             } else {
401                 sb.append(c);
402             }
403         }
404         return sb.toString();
405     }
406 
407     /** Determines the number of ordinals for a given {@link EnergyConsumerType}. */
calculateNumOrdinals(@nergyConsumerType int type, SparseArray<EnergyConsumer> idToConsumer)408     private static int calculateNumOrdinals(@EnergyConsumerType int type,
409             SparseArray<EnergyConsumer> idToConsumer) {
410         if (idToConsumer == null) return 0;
411         int numOrdinals = 0;
412         final int size = idToConsumer.size();
413         for (int idx = 0; idx < size; idx++) {
414             final EnergyConsumer consumer = idToConsumer.valueAt(idx);
415             if (consumer.type == type) numOrdinals++;
416         }
417         return numOrdinals;
418     }
419 
420     /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV)421     private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) {
422         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
423         // since the last snapshot. Round off to the nearest whole long.
424         return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
425     }
426 
427     /**
428      * Converts the EnergyConsumerDeltaData object to EnergyConsumerDetails, which can
429      * be saved in battery history.
430      */
getEnergyConsumerDetails( EnergyConsumerDeltaData delta)431     EnergyConsumerDetails getEnergyConsumerDetails(
432             EnergyConsumerDeltaData delta) {
433         if (mEnergyConsumerDetails == null) {
434             mEnergyConsumerDetails = createEnergyConsumerDetails();
435         }
436 
437         final long[] chargeUC = mEnergyConsumerDetails.chargeUC;
438         for (int i = 0; i < mEnergyConsumerDetails.consumers.length; i++) {
439             EnergyConsumerDetails.EnergyConsumer energyConsumer =
440                     mEnergyConsumerDetails.consumers[i];
441             switch (energyConsumer.type) {
442                 case EnergyConsumerType.BLUETOOTH:
443                     chargeUC[i] = delta.bluetoothChargeUC;
444                     break;
445                 case EnergyConsumerType.CPU_CLUSTER:
446                     if (delta.cpuClusterChargeUC != null) {
447                         chargeUC[i] = delta.cpuClusterChargeUC[energyConsumer.ordinal];
448                     } else {
449                         chargeUC[i] = UNAVAILABLE;
450                     }
451                     break;
452                 case EnergyConsumerType.DISPLAY:
453                     if (delta.displayChargeUC != null) {
454                         chargeUC[i] = delta.displayChargeUC[energyConsumer.ordinal];
455                     } else {
456                         chargeUC[i] = UNAVAILABLE;
457                     }
458                     break;
459                 case EnergyConsumerType.GNSS:
460                     chargeUC[i] = delta.gnssChargeUC;
461                     break;
462                 case EnergyConsumerType.MOBILE_RADIO:
463                     chargeUC[i] = delta.mobileRadioChargeUC;
464                     break;
465                 case EnergyConsumerType.WIFI:
466                     chargeUC[i] = delta.wifiChargeUC;
467                     break;
468                 case EnergyConsumerType.CAMERA:
469                     chargeUC[i] = delta.cameraChargeUC;
470                     break;
471                 case EnergyConsumerType.OTHER:
472                     if (delta.otherTotalChargeUC != null) {
473                         chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
474                     } else {
475                         chargeUC[i] = UNAVAILABLE;
476                     }
477                     break;
478                 default:
479                     chargeUC[i] = UNAVAILABLE;
480                     break;
481             }
482         }
483         return mEnergyConsumerDetails;
484     }
485 
createEnergyConsumerDetails()486     private EnergyConsumerDetails createEnergyConsumerDetails() {
487         EnergyConsumerDetails details = new EnergyConsumerDetails();
488         details.consumers =
489                 new EnergyConsumerDetails.EnergyConsumer[mEnergyConsumers.size()];
490         for (int i = 0; i < mEnergyConsumers.size(); i++) {
491             EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i);
492             EnergyConsumerDetails.EnergyConsumer consumer =
493                     new EnergyConsumerDetails.EnergyConsumer();
494             consumer.type = energyConsumer.type;
495             consumer.ordinal = energyConsumer.ordinal;
496             switch (consumer.type) {
497                 case EnergyConsumerType.BLUETOOTH:
498                     consumer.name = "BLUETOOTH";
499                     break;
500                 case EnergyConsumerType.CPU_CLUSTER:
501                     consumer.name = "CPU";
502                     break;
503                 case EnergyConsumerType.DISPLAY:
504                     consumer.name = "DISPLAY";
505                     break;
506                 case EnergyConsumerType.GNSS:
507                     consumer.name = "GNSS";
508                     break;
509                 case EnergyConsumerType.MOBILE_RADIO:
510                     consumer.name = "MOBILE_RADIO";
511                     break;
512                 case EnergyConsumerType.WIFI:
513                     consumer.name = "WIFI";
514                     break;
515                 case EnergyConsumerType.OTHER:
516                     consumer.name = sanitizeCustomBucketName(energyConsumer.name);
517                     break;
518                 default:
519                     consumer.name = "UNKNOWN";
520                     break;
521             }
522             if (consumer.type != EnergyConsumerType.OTHER) {
523                 boolean hasOrdinal = consumer.ordinal != 0;
524                 if (!hasOrdinal) {
525                     // See if any other EnergyConsumer of the same type has an ordinal
526                     for (int j = 0; j < mEnergyConsumers.size(); j++) {
527                         EnergyConsumer aConsumer = mEnergyConsumers.valueAt(j);
528                         if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
529                             hasOrdinal = true;
530                             break;
531                         }
532                     }
533                 }
534                 if (hasOrdinal) {
535                     consumer.name = consumer.name + "/" + energyConsumer.ordinal;
536                 }
537             }
538             details.consumers[i] = consumer;
539         }
540 
541         details.chargeUC = new long[details.consumers.length];
542         return details;
543     }
544 }
545