1 /*
2  * Copyright (C) 2021 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 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyLong;
23 import static org.mockito.Mockito.doAnswer;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.app.ActivityManager;
30 import android.content.Context;
31 import android.hardware.SensorManager;
32 import android.os.AggregateBatteryConsumer;
33 import android.os.BatteryConsumer;
34 import android.os.BatteryManager;
35 import android.os.BatteryStats;
36 import android.os.BatteryUsageStats;
37 import android.os.BatteryUsageStatsQuery;
38 import android.os.ConditionVariable;
39 import android.os.Parcel;
40 import android.os.Process;
41 import android.os.UidBatteryConsumer;
42 import android.platform.test.ravenwood.RavenwoodRule;
43 import android.util.SparseLongArray;
44 
45 import androidx.test.InstrumentationRegistry;
46 import androidx.test.filters.SmallTest;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.android.internal.os.BatteryStatsHistoryIterator;
50 import com.android.internal.os.MonotonicClock;
51 import com.android.internal.os.PowerProfile;
52 import com.android.server.power.stats.processor.MultiStatePowerAttributor;
53 
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.io.File;
60 import java.io.IOException;
61 import java.util.List;
62 
63 @SmallTest
64 @RunWith(AndroidJUnit4.class)
65 public class BatteryUsageStatsProviderTest {
66     @Rule(order = 0)
67     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
68             .setProvideMainThread(true)
69             .build();
70 
71     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
72     private static final long MINUTE_IN_MS = 60 * 1000;
73     private static final double PRECISION = 0.00001;
74 
75     @Rule(order = 1)
76     public final BatteryUsageStatsRule mStatsRule =
77             new BatteryUsageStatsRule(12345)
78                     .createTempDirectory()
79                     .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
80                     .setAveragePower(PowerProfile.POWER_AUDIO, 720.0)
81                     .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
82 
83     private MockClock mMockClock = mStatsRule.getMockClock();
84     private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
85     private Context mContext;
86 
87     @Before
setup()88     public void setup() throws IOException {
89         if (RavenwoodRule.isUnderRavenwood()) {
90             mContext = mock(Context.class);
91             SensorManager sensorManager = mock(SensorManager.class);
92             when(mContext.getSystemService(SensorManager.class)).thenReturn(sensorManager);
93         } else {
94             mContext = InstrumentationRegistry.getContext();
95         }
96     }
97 
98     @Test
test_getBatteryUsageStats()99     public void test_getBatteryUsageStats() throws IOException {
100         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
101 
102         final List<UidBatteryConsumer> uidBatteryConsumers =
103                 batteryUsageStats.getUidBatteryConsumers();
104         final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
105         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
106                 .isEqualTo(20 * MINUTE_IN_MS);
107         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
108                 .isEqualTo(40 * MINUTE_IN_MS);
109         assertThat(uidBatteryConsumer
110                 .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND))
111                 .isEqualTo(20 * MINUTE_IN_MS);
112         assertThat(uidBatteryConsumer
113                 .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND))
114                 .isEqualTo(20 * MINUTE_IN_MS);
115         assertThat(uidBatteryConsumer
116                 .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
117                 .isEqualTo(20 * MINUTE_IN_MS);
118         assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
119                 .isWithin(PRECISION).of(2.0);
120         assertThat(
121                 uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
122                 .isWithin(PRECISION).of(0.4);
123 
124         assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
125         assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(180 * MINUTE_IN_MS);
126         batteryUsageStats.close();
127     }
128 
129     @Test
batteryLevelInfo_charging()130     public void batteryLevelInfo_charging() throws IOException {
131         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(true);
132         assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
133         assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(1_200_000);
134         batteryUsageStats.close();
135     }
136 
137     @Test
batteryLevelInfo_onBattery()138     public void batteryLevelInfo_onBattery() throws IOException {
139         final BatteryUsageStats batteryUsageStats = prepareBatteryUsageStats(false);
140         assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000.0);
141         assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(600_000);
142         batteryUsageStats.close();
143     }
144 
145     @Test
test_selectPowerComponents()146     public void test_selectPowerComponents() throws IOException {
147         BatteryStatsImpl batteryStats = prepareBatteryStats(false);
148 
149         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
150                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
151                 mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
152                 mMonotonicClock);
153 
154         final BatteryUsageStats batteryUsageStats =
155                 provider.getBatteryUsageStats(batteryStats,
156                         new BatteryUsageStatsQuery.Builder()
157                                 .includePowerComponents(
158                                         new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO})
159                                 .build()
160                 );
161 
162         final List<UidBatteryConsumer> uidBatteryConsumers =
163                 batteryUsageStats.getUidBatteryConsumers();
164         final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
165         assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
166                 .isWithin(PRECISION).of(2.0);
167 
168         // FLASHLIGHT power estimation not requested, so the returned value is 0
169         assertThat(
170                 uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
171                 .isEqualTo(0);
172 
173         batteryUsageStats.close();
174     }
175 
prepareBatteryStats(boolean plugInAtTheEnd)176     private BatteryStatsImpl prepareBatteryStats(boolean plugInAtTheEnd) {
177         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
178         batteryStats.onSystemReady(mContext);
179 
180         synchronized (batteryStats) {
181             batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
182                     100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 0,
183                     0, 0);
184         }
185 
186         mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
187         synchronized (batteryStats) {
188             batteryStats.noteActivityResumedLocked(APP_UID);
189         }
190 
191         mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
192         synchronized (batteryStats) {
193             batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP);
194         }
195         mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
196         synchronized (batteryStats) {
197             batteryStats.noteActivityPausedLocked(APP_UID);
198         }
199         mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
200         synchronized (batteryStats) {
201             batteryStats.noteUidProcessStateLocked(APP_UID,
202                     ActivityManager.PROCESS_STATE_SERVICE);
203         }
204         mStatsRule.setTime(40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
205         synchronized (batteryStats) {
206             batteryStats.noteUidProcessStateLocked(APP_UID,
207                     ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
208         }
209         mStatsRule.setTime(50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
210         synchronized (batteryStats) {
211             batteryStats.noteUidProcessStateLocked(APP_UID,
212                     ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
213         }
214         mStatsRule.setTime(60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
215         synchronized (batteryStats) {
216             batteryStats.noteUidProcessStateLocked(APP_UID,
217                     ActivityManager.PROCESS_STATE_BOUND_TOP);
218         }
219         mStatsRule.setTime(70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
220         synchronized (batteryStats) {
221             batteryStats.noteUidProcessStateLocked(APP_UID,
222                     ActivityManager.PROCESS_STATE_CACHED_EMPTY);
223         }
224         synchronized (batteryStats) {
225             batteryStats.noteFlashlightOnLocked(APP_UID, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
226         }
227         synchronized (batteryStats) {
228             batteryStats.noteFlashlightOffLocked(APP_UID, 80 * MINUTE_IN_MS + 4000,
229                     80 * MINUTE_IN_MS + 4000);
230         }
231 
232         synchronized (batteryStats) {
233             batteryStats.noteAudioOnLocked(APP_UID, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
234         }
235         synchronized (batteryStats) {
236             batteryStats.noteAudioOffLocked(APP_UID, 90 * MINUTE_IN_MS + 10000,
237                     90 * MINUTE_IN_MS + 10000);
238         }
239 
240         synchronized (batteryStats) {
241             batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
242                     100, /* plugType */ 0, 60, 72, 3700, 2_600_000, 4_000_000, 0,
243                     100 * MINUTE_IN_MS, 100 * MINUTE_IN_MS, 21000);
244         }
245 
246         synchronized (batteryStats) {
247             batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
248                     100, /* plugType */ 0, 30, 72, 3700, 1_600_000, 4_000_000, 0,
249                     110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS, 21000);
250         }
251 
252         if (plugInAtTheEnd) {
253             synchronized (batteryStats) {
254                 batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_CHARGING,
255                         100, /* plugType */ BatteryManager.BATTERY_PLUGGED_USB, 30, 72, 3700,
256                         1_600_000, 4_000_000, /* chargeTimeToFullSeconds */ 20 * 60,
257                         120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 22000);
258             }
259         }
260 
261         setTime(180 * MINUTE_IN_MS);
262 
263         return batteryStats;
264     }
265 
prepareBatteryUsageStats(boolean plugInAtTheEnd)266     private BatteryUsageStats prepareBatteryUsageStats(boolean plugInAtTheEnd) {
267         BatteryStatsImpl batteryStats = prepareBatteryStats(plugInAtTheEnd);
268 
269         MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
270                 mock(PowerStatsStore.class), mStatsRule.getPowerProfile(),
271                 mStatsRule.getCpuScalingPolicies(), () -> 3500);
272         powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_AUDIO,
273                 true);
274         powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
275                 true);
276 
277         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
278                 powerAttributor, mStatsRule.getPowerProfile(),
279                 mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
280                 mMonotonicClock);
281 
282         return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
283     }
284 
285     @Test
testWriteAndReadHistory()286     public void testWriteAndReadHistory() throws IOException {
287         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
288         synchronized (batteryStats) {
289             batteryStats.setRecordAllHistoryLocked(true);
290         }
291         batteryStats.forceRecordAllHistory();
292 
293         batteryStats.setNoAutoReset(true);
294 
295         synchronized (batteryStats) {
296             batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
297                     100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
298                     1_000_000, 1_000_000);
299         }
300 
301         synchronized (batteryStats) {
302             batteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
303         }
304         synchronized (batteryStats) {
305             batteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
306         }
307 
308         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
309                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
310                 mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
311                 mMonotonicClock);
312 
313         final BatteryUsageStats batteryUsageStats =
314                 provider.getBatteryUsageStats(batteryStats,
315                         new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
316 
317         Parcel in = Parcel.obtain();
318         batteryUsageStats.writeToParcel(in, 0);
319         batteryUsageStats.close();
320 
321         final byte[] bytes = in.marshall();
322 
323         Parcel out = Parcel.obtain();
324         out.unmarshall(bytes, 0, bytes.length);
325         out.setDataPosition(0);
326 
327         BatteryUsageStats unparceled = BatteryUsageStats.CREATOR.createFromParcel(out);
328 
329         final BatteryStatsHistoryIterator iterator =
330                 unparceled.iterateBatteryStatsHistory();
331         BatteryStats.HistoryItem item;
332 
333         assertThat(item = iterator.next()).isNotNull();
334         assertHistoryItem(item,
335                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
336                 null, 0, 3_600_000, 90, 1_000_000);
337 
338         assertThat(item = iterator.next()).isNotNull();
339         assertHistoryItem(item,
340                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
341                 null, 0, 3_600_000, 90, 1_000_000);
342         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0);
343 
344         assertThat(item = iterator.next()).isNotNull();
345         assertHistoryItem(item,
346                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
347                 null, 0, 3_600_000, 90, 2_000_000);
348         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0);
349 
350         assertThat(item = iterator.next()).isNotNull();
351         assertHistoryItem(item,
352                 BatteryStats.HistoryItem.CMD_UPDATE,
353                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
354                 "foo", APP_UID, 3_600_000, 90, 3_000_000);
355 
356         assertThat(item = iterator.next()).isNotNull();
357         assertHistoryItem(item,
358                 BatteryStats.HistoryItem.CMD_UPDATE,
359                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
360                 "foo", APP_UID, 3_600_000, 90, 3_001_000);
361 
362         assertThat(iterator.hasNext()).isFalse();
363         assertThat(iterator.next()).isNull();
364 
365         unparceled.close();
366     }
367 
368     @Test
testWriteAndReadHistoryTags()369     public void testWriteAndReadHistoryTags() throws IOException {
370         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
371         synchronized (batteryStats) {
372             batteryStats.setRecordAllHistoryLocked(true);
373         }
374         batteryStats.forceRecordAllHistory();
375 
376         batteryStats.setNoAutoReset(true);
377 
378         mStatsRule.setTime(1_000_000, 1_000_000);
379 
380         synchronized (batteryStats) {
381             batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
382                     100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
383                     1_000_000, 1_000_000);
384         }
385 
386         // Add a large number of different history tags with strings of increasing length.
387         // These long strings will overflow the history buffer, at which point
388         // history will be written to disk and a new buffer started.
389         // As a result, we will only see a tail end of the sequence of events included
390         // in history.
391         for (int i = 1; i < 200; i++) {
392             StringBuilder sb = new StringBuilder().append(i).append(" ");
393             for (int j = 0; j <= i; j++) {
394                 sb.append("word ");
395             }
396             final int uid = i;
397             synchronized (batteryStats) {
398                 batteryStats.noteJobStartLocked(sb.toString(), uid);
399             }
400         }
401 
402         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
403                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
404                 mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
405                 mMonotonicClock);
406 
407         final BatteryUsageStats batteryUsageStats =
408                 provider.getBatteryUsageStats(batteryStats,
409                         new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
410 
411         Parcel parcel = Parcel.obtain();
412         parcel.writeParcelable(batteryUsageStats, 0);
413 
414         if (!RavenwoodRule.isUnderRavenwood()) {
415             assertThat(parcel.dataSize()).isAtMost(128_000);
416         }
417 
418         batteryUsageStats.close();
419 
420         parcel.setDataPosition(0);
421 
422         BatteryUsageStats unparceled = parcel.readParcelable(getClass().getClassLoader(),
423                 BatteryUsageStats.class);
424 
425         BatteryStatsHistoryIterator iterator = unparceled.iterateBatteryStatsHistory();
426         BatteryStats.HistoryItem item;
427 
428         assertThat(item = iterator.next()).isNotNull();
429         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
430 
431         int expectedUid = 1;
432         while ((item = iterator.next()) != null) {
433             while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
434                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
435                 assertThat(item = iterator.next()).isNotNull();
436             }
437             int uid = item.eventTag.uid;
438             assertThat(uid).isEqualTo(expectedUid++);
439             assertThat(item.eventCode).isEqualTo(
440                     BatteryStats.HistoryItem.EVENT_JOB | BatteryStats.HistoryItem.EVENT_FLAG_START);
441             assertThat(item.eventTag.string).startsWith(uid + " ");
442             assertThat(item.batteryChargeUah).isEqualTo(3_600_000);
443             assertThat(item.batteryLevel).isEqualTo(90);
444             assertThat(item.time).isEqualTo((long) 1_000_000);
445         }
446 
447         assertThat(expectedUid).isEqualTo(200);
448     }
449 
assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode, String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs)450     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
451             String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) {
452         assertThat(item.cmd).isEqualTo(command);
453         assertThat(item.eventCode).isEqualTo(eventCode);
454         if (tag == null) {
455             assertThat(item.eventTag).isNull();
456         } else {
457             assertThat(item.eventTag.string).isEqualTo(tag);
458             assertThat(item.eventTag.uid).isEqualTo(uid);
459         }
460         assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
461         assertThat(item.batteryLevel).isEqualTo(batteryLevel);
462 
463         assertThat(item.time).isEqualTo(elapsedTimeMs);
464     }
465 
466     @Test
shouldUpdateStats()467     public void shouldUpdateStats() {
468         final List<BatteryUsageStatsQuery> queries = List.of(
469                 new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(),
470                 new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build()
471         );
472 
473         assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries,
474                 10500, 10000)).isFalse();
475 
476         assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries,
477                 11500, 10000)).isTrue();
478     }
479 
480     @Test
testAggregateBatteryStats()481     public void testAggregateBatteryStats() throws IOException {
482         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
483 
484         setTime(5 * MINUTE_IN_MS);
485         synchronized (batteryStats) {
486             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
487         }
488 
489         PowerStatsStore powerStatsStore = new PowerStatsStore(
490                 new File(mStatsRule.getHistoryDir(), "powerstatsstore"),
491                 mStatsRule.getHandler());
492         powerStatsStore.reset();
493 
494         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
495                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
496                 mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
497                 mMonotonicClock);
498 
499         batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
500                 /* accumulateBatteryUsageStats */ false);
501         synchronized (batteryStats) {
502             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
503         }
504 
505         synchronized (batteryStats) {
506             batteryStats.noteFlashlightOnLocked(APP_UID,
507                     10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
508         }
509         synchronized (batteryStats) {
510             batteryStats.noteFlashlightOffLocked(APP_UID,
511                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
512         }
513         setTime(25 * MINUTE_IN_MS);
514         synchronized (batteryStats) {
515             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
516         }
517 
518         synchronized (batteryStats) {
519             batteryStats.noteFlashlightOnLocked(APP_UID,
520                     30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
521         }
522         synchronized (batteryStats) {
523             batteryStats.noteFlashlightOffLocked(APP_UID,
524                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
525         }
526         setTime(55 * MINUTE_IN_MS);
527         synchronized (batteryStats) {
528             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
529         }
530 
531         // This section should be ignored because the timestamp is out or range
532         synchronized (batteryStats) {
533             batteryStats.noteFlashlightOnLocked(APP_UID,
534                     60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
535         }
536         synchronized (batteryStats) {
537             batteryStats.noteFlashlightOffLocked(APP_UID,
538                     70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
539         }
540         setTime(75 * MINUTE_IN_MS);
541         synchronized (batteryStats) {
542             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
543         }
544 
545         // This section should be ignored because it represents the current stats session
546         synchronized (batteryStats) {
547             batteryStats.noteFlashlightOnLocked(APP_UID,
548                     80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
549         }
550         synchronized (batteryStats) {
551             batteryStats.noteFlashlightOffLocked(APP_UID,
552                     90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
553         }
554         setTime(95 * MINUTE_IN_MS);
555 
556         // Await completion
557         ConditionVariable done = new ConditionVariable();
558         mStatsRule.getHandler().post(done::open);
559         done.block();
560 
561         // Include the first and the second snapshot, but not the third or current
562         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
563                 .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS)
564                 .build();
565         final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query);
566 
567         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
568         assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS);
569         assertThat(stats.getAggregateBatteryConsumer(
570                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
571                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
572                 .isWithin(0.0001)
573                 .of(180.0);  // 360 mA * 0.5 hours
574         assertThat(stats.getAggregateBatteryConsumer(
575                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
576                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
577                 .isEqualTo((10 + 20) * MINUTE_IN_MS);
578         final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
579                 .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
580         assertThat(uidBatteryConsumer
581                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
582                 .isWithin(0.1)
583                 .of(180.0);
584 
585         stats.close();
586     }
587 
588     @Test
accumulateBatteryUsageStats()589     public void accumulateBatteryUsageStats() throws Throwable {
590         accumulateBatteryUsageStats(10000000, 1);
591         // Accumulate every 200 bytes of battery history
592         accumulateBatteryUsageStats(200, 2);
593         accumulateBatteryUsageStats(50, 5);
594         // Accumulate on every invocation of accumulateBatteryUsageStats
595         accumulateBatteryUsageStats(0, 7);
596     }
597 
accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize, int expectedNumberOfUpdates)598     private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
599             int expectedNumberOfUpdates) throws Throwable {
600         BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
601         // Note - these two are in microseconds
602         when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
603         when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
604         batteryStats.forceRecordAllHistory();
605 
606         setTime(5 * MINUTE_IN_MS);
607 
608         // Capture the session start timestamp
609         synchronized (batteryStats) {
610             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
611         }
612 
613         PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
614                 new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
615                 mStatsRule.getHandler()));
616         powerStatsStore.reset();
617 
618         int[] count = new int[1];
619         doAnswer(inv -> {
620             count[0]++;
621             return null;
622         }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
623 
624         MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
625                 powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
626                 () -> 3500);
627         for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
628                 powerComponentId++) {
629             powerAttributor.setPowerComponentSupported(powerComponentId, true);
630         }
631         powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
632 
633         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
634                 powerAttributor, mStatsRule.getPowerProfile(),
635                 mStatsRule.getCpuScalingPolicies(), powerStatsStore,
636                 accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
637 
638         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
639 
640         synchronized (batteryStats) {
641             batteryStats.noteFlashlightOnLocked(APP_UID,
642                     10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
643         }
644 
645         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
646 
647         synchronized (batteryStats) {
648             batteryStats.noteFlashlightOffLocked(APP_UID,
649                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
650         }
651 
652         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
653 
654         synchronized (batteryStats) {
655             batteryStats.noteFlashlightOnLocked(APP_UID,
656                     30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
657         }
658 
659         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
660 
661         synchronized (batteryStats) {
662             batteryStats.noteFlashlightOffLocked(APP_UID,
663                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
664         }
665         setTime(55 * MINUTE_IN_MS);
666 
667         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
668 
669         // This section has not been saved yet, but should be added to the accumulated totals
670         synchronized (batteryStats) {
671             batteryStats.noteFlashlightOnLocked(APP_UID,
672                     80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
673         }
674 
675         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
676 
677         synchronized (batteryStats) {
678             batteryStats.noteFlashlightOffLocked(APP_UID,
679                     110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
680         }
681         setTime(115 * MINUTE_IN_MS);
682 
683         // Pick up the remainder of battery history that has not yet been accumulated
684         provider.accumulateBatteryUsageStats(batteryStats);
685 
686         mStatsRule.waitForBackgroundThread();
687 
688         BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
689                 new BatteryUsageStatsQuery.Builder().accumulated().build());
690 
691         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
692         assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
693 
694         assertThat(stats.getBatteryTimeRemainingMs()).isEqualTo(111);
695         assertThat(stats.getChargeTimeRemainingMs()).isEqualTo(777);
696         assertThat(stats.getBatteryCapacity()).isEqualTo(4000);  // from PowerProfile
697 
698         // Total: 10 + 20 + 30 = 60
699         assertThat(stats.getAggregateBatteryConsumer(
700                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
701                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
702                 .isWithin(0.0001)
703                 .of(360.0);  // 360 mA * 1.0 hour
704         assertThat(stats.getAggregateBatteryConsumer(
705                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
706                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
707                 .isEqualTo(60 * MINUTE_IN_MS);
708 
709         final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
710                 .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
711         assertThat(uidBatteryConsumer
712                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
713                 .isWithin(0.1)
714                 .of(360.0);
715         assertThat(uidBatteryConsumer
716                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
717                 .isEqualTo(60 * MINUTE_IN_MS);
718 
719         assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
720 
721         stats.close();
722     }
723 
setTime(long timeMs)724     private void setTime(long timeMs) {
725         mMockClock.currentTime = timeMs;
726         mMockClock.realtime = timeMs;
727     }
728 
729     @Test
saveBatteryUsageStatsOnReset_incompatibleEnergyConsumers()730     public void saveBatteryUsageStatsOnReset_incompatibleEnergyConsumers() throws Throwable {
731         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
732         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
733         int componentId0 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
734         int componentId1 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1;
735 
736         synchronized (batteryStats) {
737             batteryStats.getUidStatsLocked(APP_UID);
738 
739             SparseLongArray uidEnergies = new SparseLongArray();
740             uidEnergies.put(APP_UID, 30_000_000);
741             batteryStats.updateCustomEnergyConsumerStatsLocked(0, 100_000_000, uidEnergies);
742             batteryStats.updateCustomEnergyConsumerStatsLocked(1, 200_000_000, uidEnergies);
743         }
744 
745         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
746                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
747                 mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
748                 mMonotonicClock);
749 
750         PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
751         doAnswer(invocation -> {
752             BatteryUsageStats stats = invocation.getArgument(1);
753             AggregateBatteryConsumer device = stats.getAggregateBatteryConsumer(
754                     BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
755             assertThat(device.getCustomPowerComponentName(componentId0)).isEqualTo("FOO");
756             assertThat(device.getCustomPowerComponentName(componentId1)).isEqualTo("BAR");
757             assertThat(device.getConsumedPower(componentId0))
758                     .isWithin(PRECISION).of(27.77777);
759             assertThat(device.getConsumedPower(componentId1))
760                     .isWithin(PRECISION).of(55.55555);
761 
762             UidBatteryConsumer uid = stats.getUidBatteryConsumers().get(0);
763             assertThat(uid.getConsumedPower(componentId0))
764                     .isWithin(PRECISION).of(8.33333);
765             assertThat(uid.getConsumedPower(componentId1))
766                     .isWithin(PRECISION).of(8.33333);
767             stats.close();
768             return null;
769         }).when(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
770 
771         mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore,
772                 /* accumulateBatteryUsageStats */ false);
773 
774         // Make an incompatible change of supported energy components.  This will trigger
775         // a BatteryStats reset, which will generate a snapshot of battery stats.
776         mStatsRule.initMeasuredEnergyStatsLocked(
777                 new String[]{"COMPONENT1"});
778 
779         mStatsRule.waitForBackgroundThread();
780 
781         verify(powerStatsStore).storeBatteryUsageStatsAsync(anyLong(), any());
782     }
783 
784     @Test
testAggregateBatteryStats_incompatibleSnapshot()785     public void testAggregateBatteryStats_incompatibleSnapshot() throws IOException {
786         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
787         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
788 
789         PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
790 
791         PowerStatsSpan span0 = new PowerStatsSpan(0);
792         span0.addTimeFrame(0, 1000, 1234);
793         span0.addSection(new BatteryUsageStatsSection(
794                 new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
795                         .setStatsDuration(1234).build()));
796 
797         PowerStatsSpan span1 = new PowerStatsSpan(1);
798         span1.addTimeFrame(0, 2000, 4321);
799         span1.addSection(new BatteryUsageStatsSection(
800                 new BatteryUsageStats.Builder(new String[]{"different"})
801                         .setStatsDuration(4321).build()));
802 
803         when(powerStatsStore.getTableOfContents()).thenReturn(
804                 List.of(span0.getMetadata(), span1.getMetadata()));
805 
806         when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
807                 .thenReturn(span0);
808         when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
809                 .thenReturn(span1);
810 
811         span1.close();
812 
813         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
814                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
815                 mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
816                 mMonotonicClock);
817 
818         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
819                 .aggregateSnapshots(0, 3000)
820                 .build();
821         final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query);
822         assertThat(stats.getCustomPowerComponentNames())
823                 .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
824         assertThat(stats.getStatsDuration()).isEqualTo(1234);
825 
826         stats.close();
827     }
828 }
829