1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.power;
16 
17 import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
18 import static android.provider.Settings.Global.SHOW_USB_TEMPERATURE_ALARM;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.Matchers.eq;
24 import static org.mockito.Mockito.anyObject;
25 import static org.mockito.Mockito.never;
26 import static org.mockito.Mockito.times;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.content.BroadcastReceiver;
31 import android.content.IntentFilter;
32 import android.os.BatteryManager;
33 import android.os.Handler;
34 import android.os.IThermalEventListener;
35 import android.os.IThermalService;
36 import android.os.PowerManager;
37 import android.os.Temperature;
38 import android.provider.Settings;
39 import android.test.suitebuilder.annotation.SmallTest;
40 import android.testing.AndroidTestingRunner;
41 import android.testing.TestableLooper;
42 import android.testing.TestableLooper.RunWithLooper;
43 import android.testing.TestableResources;
44 
45 import com.android.settingslib.fuelgauge.Estimate;
46 import com.android.systemui.R;
47 import com.android.systemui.SysuiTestCase;
48 import com.android.systemui.broadcast.BroadcastDispatcher;
49 import com.android.systemui.keyguard.WakefulnessLifecycle;
50 import com.android.systemui.power.PowerUI.WarningsUI;
51 import com.android.systemui.settings.UserTracker;
52 import com.android.systemui.statusbar.CommandQueue;
53 import com.android.systemui.statusbar.phone.CentralSurfaces;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.Mock;
59 import org.mockito.MockitoAnnotations;
60 
61 import java.time.Duration;
62 import java.util.Optional;
63 import java.util.concurrent.TimeUnit;
64 
65 import dagger.Lazy;
66 
67 @RunWith(AndroidTestingRunner.class)
68 @RunWithLooper
69 @SmallTest
70 public class PowerUITest extends SysuiTestCase {
71 
72     private static final boolean UNPLUGGED = false;
73     private static final boolean POWER_SAVER_OFF = false;
74     private static final int ABOVE_WARNING_BUCKET = 1;
75     private static final long ONE_HOUR_MILLIS = Duration.ofHours(1).toMillis();
76     public static final int BELOW_WARNING_BUCKET = -1;
77     public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
78     public static final long BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(30);
79     public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
80     private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
81     private static final int OLD_BATTERY_LEVEL_NINE = 9;
82     private static final int OLD_BATTERY_LEVEL_10 = 10;
83     private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15);
84     public static final int BATTERY_LEVEL_10 = 10;
85     @Mock private WarningsUI mMockWarnings;
86     private PowerUI mPowerUI;
87     @Mock private EnhancedEstimates mEnhancedEstimates;
88     @Mock private PowerManager mPowerManager;
89     @Mock private UserTracker mUserTracker;
90     @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
91     @Mock private IThermalService mThermalServiceMock;
92     private IThermalEventListener mUsbThermalEventListener;
93     private IThermalEventListener mSkinThermalEventListener;
94     @Mock private BroadcastDispatcher mBroadcastDispatcher;
95     @Mock private CommandQueue mCommandQueue;
96     @Mock private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
97     @Mock private CentralSurfaces mCentralSurfaces;
98 
99     @Before
setup()100     public void setup() {
101         MockitoAnnotations.initMocks(this);
102 
103         when(mCentralSurfacesOptionalLazy.get()).thenReturn(Optional.of(mCentralSurfaces));
104 
105         createPowerUi();
106         mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
107         mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
108     }
109 
110     @Test
testReceiverIsRegisteredToDispatcherOnStart()111     public void testReceiverIsRegisteredToDispatcherOnStart() {
112         mPowerUI.start();
113         verify(mBroadcastDispatcher).registerReceiverWithHandler(
114                 any(BroadcastReceiver.class),
115                 any(IntentFilter.class),
116                 any(Handler.class)); //PowerUI does not call with User
117     }
118 
119     @Test
testSkinWarning_throttlingCritical()120     public void testSkinWarning_throttlingCritical() throws Exception {
121         mPowerUI.start();
122 
123         final Temperature temp = getCriticalStatusTemp(Temperature.TYPE_SKIN, "skin1");
124         mSkinThermalEventListener.notifyThrottling(temp);
125 
126         // dismiss skin high temperature warning when throttling status is critical
127         TestableLooper.get(this).processAllMessages();
128         verify(mMockWarnings, never()).showHighTemperatureWarning();
129         verify(mMockWarnings, times(1)).dismissHighTemperatureWarning();
130     }
131 
132     @Test
testSkinWarning_throttlingEmergency()133     public void testSkinWarning_throttlingEmergency() throws Exception {
134         mPowerUI.start();
135 
136         final Temperature temp = getEmergencyStatusTemp(Temperature.TYPE_SKIN, "skin2");
137         mSkinThermalEventListener.notifyThrottling(temp);
138 
139         // show skin high temperature warning when throttling status is emergency
140         TestableLooper.get(this).processAllMessages();
141         verify(mMockWarnings, times(1)).showHighTemperatureWarning();
142         verify(mMockWarnings, never()).dismissHighTemperatureWarning();
143     }
144 
145     @Test
testUsbAlarm_throttlingCritical()146     public void testUsbAlarm_throttlingCritical() throws Exception {
147         mPowerUI.start();
148 
149         final Temperature temp = getCriticalStatusTemp(Temperature.TYPE_USB_PORT, "usb1");
150         mUsbThermalEventListener.notifyThrottling(temp);
151 
152         // not show usb high temperature alarm when throttling status is critical
153         TestableLooper.get(this).processAllMessages();
154         verify(mMockWarnings, never()).showUsbHighTemperatureAlarm();
155     }
156 
157     @Test
testUsbAlarm_throttlingEmergency()158     public void testUsbAlarm_throttlingEmergency() throws Exception {
159         mPowerUI.start();
160 
161         final Temperature temp = getEmergencyStatusTemp(Temperature.TYPE_USB_PORT, "usb2");
162         mUsbThermalEventListener.notifyThrottling(temp);
163 
164         // show usb high temperature alarm when throttling status is emergency
165         TestableLooper.get(this).processAllMessages();
166         verify(mMockWarnings, times(1)).showUsbHighTemperatureAlarm();
167     }
168 
169     @Test
testSettingOverrideConfig_enableSkinTemperatureWarning()170     public void testSettingOverrideConfig_enableSkinTemperatureWarning() throws Exception {
171         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
172         TestableResources resources = mContext.getOrCreateTestableResources();
173         resources.addOverride(R.integer.config_showTemperatureWarning, 0);
174 
175         mPowerUI.start();
176 
177         TestableLooper.get(this).processAllMessages();
178         verify(mThermalServiceMock, times(1))
179                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
180     }
181 
182     @Test
testSettingOverrideConfig_enableUsbTemperatureAlarm()183     public void testSettingOverrideConfig_enableUsbTemperatureAlarm() throws Exception {
184         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
185         TestableResources resources = mContext.getOrCreateTestableResources();
186         resources.addOverride(R.integer.config_showUsbPortAlarm, 0);
187 
188         mPowerUI.start();
189 
190         TestableLooper.get(this).processAllMessages();
191         verify(mThermalServiceMock, times(1))
192                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
193     }
194 
195     @Test
testSettingOverrideConfig_disableSkinTemperatureWarning()196     public void testSettingOverrideConfig_disableSkinTemperatureWarning() throws Exception {
197         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
198         TestableResources resources = mContext.getOrCreateTestableResources();
199         resources.addOverride(R.integer.config_showTemperatureWarning, 1);
200 
201         mPowerUI.start();
202 
203         TestableLooper.get(this).processAllMessages();
204         verify(mThermalServiceMock, times(0))
205                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
206     }
207 
208     @Test
testSettingOverrideConfig_disableUsbTemperatureAlarm()209     public void testSettingOverrideConfig_disableUsbTemperatureAlarm() throws Exception {
210         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
211         TestableResources resources = mContext.getOrCreateTestableResources();
212         resources.addOverride(R.integer.config_showUsbPortAlarm, 1);
213 
214         mPowerUI.start();
215 
216         TestableLooper.get(this).processAllMessages();
217         verify(mThermalServiceMock, times(0))
218                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
219     }
220 
221     @Test
testThermalEventListenerRegistration_success_skinType()222     public void testThermalEventListenerRegistration_success_skinType() throws Exception {
223         // Settings SHOW_TEMPERATURE_WARNING is set to 1
224         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
225 
226         // success registering skin thermal event listener
227         when(mThermalServiceMock.registerThermalEventListenerWithType(
228                 anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(true);
229 
230         mPowerUI.doSkinThermalEventListenerRegistration();
231 
232         // verify registering skin thermal event listener, return true (success)
233         TestableLooper.get(this).processAllMessages();
234         verify(mThermalServiceMock, times(1))
235                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
236 
237         // Settings SHOW_TEMPERATURE_WARNING is set to 0
238         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
239 
240         mPowerUI.doSkinThermalEventListenerRegistration();
241 
242         // verify unregistering skin thermal event listener
243         TestableLooper.get(this).processAllMessages();
244         verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
245     }
246 
247     @Test
testThermalEventListenerRegistration_fail_skinType()248     public void testThermalEventListenerRegistration_fail_skinType() throws Exception {
249         // Settings SHOW_TEMPERATURE_WARNING is set to 1
250         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
251 
252         // fail registering skin thermal event listener
253         when(mThermalServiceMock.registerThermalEventListenerWithType(
254                 anyObject(), eq(Temperature.TYPE_SKIN))).thenReturn(false);
255 
256         mPowerUI.doSkinThermalEventListenerRegistration();
257 
258         // verify registering skin thermal event listener, return false (fail)
259         TestableLooper.get(this).processAllMessages();
260         verify(mThermalServiceMock, times(1))
261                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
262 
263         // Settings SHOW_TEMPERATURE_WARNING is set to 0
264         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 0);
265 
266         mPowerUI.doSkinThermalEventListenerRegistration();
267 
268         // verify that cannot unregister listener (current state is unregistered)
269         TestableLooper.get(this).processAllMessages();
270         verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
271 
272         // Settings SHOW_TEMPERATURE_WARNING is set to 1
273         Settings.Global.putInt(mContext.getContentResolver(), SHOW_TEMPERATURE_WARNING, 1);
274 
275         mPowerUI.doSkinThermalEventListenerRegistration();
276 
277         // verify that can register listener (current state is unregistered)
278         TestableLooper.get(this).processAllMessages();
279         verify(mThermalServiceMock, times(2))
280                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_SKIN));
281     }
282 
283     @Test
testThermalEventListenerRegistration_success_usbType()284     public void testThermalEventListenerRegistration_success_usbType() throws Exception {
285         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 1
286         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
287 
288         // success registering usb thermal event listener
289         when(mThermalServiceMock.registerThermalEventListenerWithType(
290                 anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(true);
291 
292         mPowerUI.doUsbThermalEventListenerRegistration();
293 
294         // verify registering usb thermal event listener, return true (success)
295         TestableLooper.get(this).processAllMessages();
296         verify(mThermalServiceMock, times(1))
297                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
298 
299         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
300         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
301 
302         // verify unregistering usb thermal event listener
303         mPowerUI.doUsbThermalEventListenerRegistration();
304         TestableLooper.get(this).processAllMessages();
305         verify(mThermalServiceMock, times(1)).unregisterThermalEventListener(anyObject());
306     }
307 
308     @Test
testThermalEventListenerRegistration_fail_usbType()309     public void testThermalEventListenerRegistration_fail_usbType() throws Exception {
310         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 1
311         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
312 
313         // fail registering usb thermal event listener
314         when(mThermalServiceMock.registerThermalEventListenerWithType(
315                 anyObject(), eq(Temperature.TYPE_USB_PORT))).thenReturn(false);
316 
317         mPowerUI.doUsbThermalEventListenerRegistration();
318 
319         // verify registering usb thermal event listener, return false (fail)
320         TestableLooper.get(this).processAllMessages();
321         verify(mThermalServiceMock, times(1))
322                 .registerThermalEventListenerWithType(anyObject(), eq(Temperature.TYPE_USB_PORT));
323 
324         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 0
325         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 0);
326 
327         mPowerUI.doUsbThermalEventListenerRegistration();
328 
329         // verify that cannot unregister listener (current state is unregistered)
330         TestableLooper.get(this).processAllMessages();
331         verify(mThermalServiceMock, times(0)).unregisterThermalEventListener(anyObject());
332 
333         // Settings SHOW_USB_TEMPERATURE_ALARM is set to 1
334         Settings.Global.putInt(mContext.getContentResolver(), SHOW_USB_TEMPERATURE_ALARM, 1);
335 
336         mPowerUI.doUsbThermalEventListenerRegistration();
337 
338         // verify that can register listener (current state is unregistered)
339         TestableLooper.get(this).processAllMessages();
340         verify(mThermalServiceMock, times(2)).registerThermalEventListenerWithType(
341                 anyObject(), eq(Temperature.TYPE_USB_PORT));
342     }
343 
344     @Test
testMaybeShowHybridWarning()345     public void testMaybeShowHybridWarning() {
346         mPowerUI.start();
347 
348         // verify low warning shown this cycle noticed
349         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
350         BatteryStateSnapshot lastState = state.get();
351         state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
352         state.mBatteryLevel = 15;
353 
354         mPowerUI.maybeShowHybridWarning(state.get(), lastState);
355 
356         assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
357         assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
358 
359         // verify severe warning noticed this cycle
360         lastState = state.get();
361         state.mBatteryLevel = 1;
362         state.mTimeRemainingMillis = Duration.ofMinutes(10).toMillis();
363 
364         mPowerUI.maybeShowHybridWarning(state.get(), lastState);
365 
366         assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
367         assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isTrue();
368 
369         // verify getting past threshold resets values
370         lastState = state.get();
371         state.mBatteryLevel = 100;
372         state.mTimeRemainingMillis = Duration.ofDays(1).toMillis();
373 
374         mPowerUI.maybeShowHybridWarning(state.get(), lastState);
375 
376         assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isFalse();
377         assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
378     }
379 
380     @Test
testShouldShowHybridWarning_lowLevelWarning()381     public void testShouldShowHybridWarning_lowLevelWarning() {
382         mPowerUI.start();
383         mPowerUI.mLowWarningShownThisChargeCycle = false;
384         mPowerUI.mSevereWarningShownThisChargeCycle = false;
385         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
386 
387         // readiness check to make sure we can show for a valid config
388         state.mBatteryLevel = 10;
389         state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
390         boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
391         assertThat(shouldShow).isTrue();
392 
393         // Shouldn't show if plugged in
394         state.mPlugged = true;
395         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
396         assertThat(shouldShow).isFalse();
397 
398         // Shouldn't show if battery is unknown
399         state.mPlugged = false;
400         state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
401         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
402         assertThat(shouldShow).isFalse();
403 
404         state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
405         // Already shown both warnings
406         mPowerUI.mLowWarningShownThisChargeCycle = true;
407         mPowerUI.mSevereWarningShownThisChargeCycle = true;
408         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
409         assertThat(shouldShow).isFalse();
410 
411         // Can show low warning
412         mPowerUI.mLowWarningShownThisChargeCycle = false;
413         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
414         assertThat(shouldShow).isTrue();
415 
416         // Can't show if above the threshold for time & battery
417         state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
418         state.mBatteryLevel = 100;
419         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
420         assertThat(shouldShow).isFalse();
421 
422         // Battery under low percentage threshold but not time
423         state.mBatteryLevel = 10;
424         state.mLowLevelThreshold = 50;
425         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
426         assertThat(shouldShow).isTrue();
427 
428         // Should also trigger if both level and time remaining under low threshold
429         state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
430         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
431         assertThat(shouldShow).isTrue();
432 
433         // battery saver should block the low level warning though
434         state.mIsPowerSaver = true;
435         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
436         assertThat(shouldShow).isFalse();
437     }
438 
439     @Test
testShouldShowHybridWarning_severeLevelWarning()440     public void testShouldShowHybridWarning_severeLevelWarning() {
441         mPowerUI.start();
442         mPowerUI.mLowWarningShownThisChargeCycle = false;
443         mPowerUI.mSevereWarningShownThisChargeCycle = false;
444         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
445 
446         // readiness check to make sure we can show for a valid config
447         state.mBatteryLevel = 1;
448         state.mTimeRemainingMillis = Duration.ofMinutes(1).toMillis();
449         boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
450         assertThat(shouldShow).isTrue();
451 
452         // Shouldn't show if plugged in
453         state.mPlugged = true;
454         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
455         assertThat(shouldShow).isFalse();
456 
457         // Shouldn't show if battery is unknown
458         state.mPlugged = false;
459         state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
460         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
461         assertThat(shouldShow).isFalse();
462 
463         state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
464         // Already shown both warnings
465         mPowerUI.mLowWarningShownThisChargeCycle = true;
466         mPowerUI.mSevereWarningShownThisChargeCycle = true;
467         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
468         assertThat(shouldShow).isFalse();
469 
470         // Can show severe warning
471         mPowerUI.mSevereWarningShownThisChargeCycle = false;
472         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
473         assertThat(shouldShow).isTrue();
474 
475         // Can't show if above the threshold for time & battery
476         state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
477         state.mBatteryLevel = 100;
478         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
479         assertThat(shouldShow).isFalse();
480 
481         // Battery under low percentage threshold but not time
482         state.mBatteryLevel = 1;
483         state.mSevereLevelThreshold = 5;
484         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
485         assertThat(shouldShow).isTrue();
486 
487         // Should also trigger if both level and time remaining under low threshold
488         state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
489         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
490         assertThat(shouldShow).isTrue();
491 
492         // battery saver should not block the severe level warning though
493         state.mIsPowerSaver = true;
494         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
495         assertThat(shouldShow).isTrue();
496     }
497 
498     @Test
testShouldDismissHybridWarning()499     public void testShouldDismissHybridWarning() {
500         mPowerUI.start();
501         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
502 
503         // We should dismiss if the device is plugged in
504         state.mPlugged = true;
505         state.mBatteryLevel = 19;
506         state.mLowLevelThreshold = 20;
507         boolean shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
508         assertThat(shouldDismiss).isTrue();
509 
510         // If not plugged in and below the threshold we should not dismiss
511         state.mPlugged = false;
512         shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
513         assertThat(shouldDismiss).isFalse();
514 
515         // If we go over the low warning threshold we should dismiss
516         state.mBatteryLevel = 21;
517         shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
518         assertThat(shouldDismiss).isTrue();
519     }
520 
521     @Test
testRefreshEstimateIfNeeded_onlyQueriesEstimateOnBatteryLevelChangeOrNull()522     public void testRefreshEstimateIfNeeded_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
523         mPowerUI.start();
524         Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true, 0);
525         when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
526         when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
527         when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
528         when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
529         mPowerUI.mBatteryLevel = 10;
530 
531         // we expect that the first time it will query since there is no last battery snapshot.
532         // However an invalid estimate (-1) is returned.
533         Estimate refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
534         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
535         BatteryStateSnapshot snapshot = new BatteryStateSnapshot(
536                 BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD,
537                 0, 0, -1, 0, 0, 0, false, true);
538         mPowerUI.mLastBatteryStateSnapshot = snapshot;
539 
540         // query again since the estimate was -1
541         estimate = new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true, 0);
542         when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
543         refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
544         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
545         snapshot = new BatteryStateSnapshot(
546                 BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0,
547                 0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, 0, false, true);
548         mPowerUI.mLastBatteryStateSnapshot = snapshot;
549 
550         // Battery level hasn't changed, so we don't query again
551         estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true, 0);
552         when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
553         refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
554         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
555 
556         // Battery level changes so we update again
557         mPowerUI.mBatteryLevel = 9;
558         refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
559         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
560     }
561 
562     @Test
testShouldShowStandardWarning()563     public void testShouldShowStandardWarning() {
564         mPowerUI.start();
565         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
566         state.mIsHybrid = false;
567         BatteryStateSnapshot lastState = state.get();
568 
569         // readiness check to make sure we can show for a valid config
570         state.mBatteryLevel = 10;
571         state.mBucket = -1;
572         boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
573         assertThat(shouldShow).isTrue();
574         lastState = state.get();
575 
576         // Shouldn't show if plugged in
577         state.mPlugged = true;
578         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
579         assertThat(shouldShow).isFalse();
580 
581         state.mPlugged = false;
582         // Shouldn't show if battery saver
583         state.mIsPowerSaver = true;
584         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
585         assertThat(shouldShow).isFalse();
586 
587         state.mIsPowerSaver = false;
588         // Shouldn't show if battery is unknown
589         state.mPlugged = false;
590         state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
591         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
592         assertThat(shouldShow).isFalse();
593 
594         state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
595         // show if plugged -> unplugged, bucket -1 -> -1
596         state.mPlugged = true;
597         state.mBucket = -1;
598         lastState = state.get();
599         state.mPlugged = false;
600         state.mBucket = -1;
601         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
602         assertThat(shouldShow).isTrue();
603 
604         // don't show if plugged -> unplugged, bucket 0 -> 0
605         state.mPlugged = true;
606         state.mBucket = 0;
607         lastState = state.get();
608         state.mPlugged = false;
609         state.mBucket = 0;
610         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
611         assertThat(shouldShow).isFalse();
612 
613         // show if unplugged -> unplugged, bucket 0 -> -1
614         state.mPlugged = false;
615         state.mBucket = 0;
616         lastState = state.get();
617         state.mPlugged = false;
618         state.mBucket = -1;
619         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
620         assertThat(shouldShow).isTrue();
621 
622         // don't show if unplugged -> unplugged, bucket -1 -> 1
623         state.mPlugged = false;
624         state.mBucket = -1;
625         lastState = state.get();
626         state.mPlugged = false;
627         state.mBucket = 1;
628         shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
629         assertThat(shouldShow).isFalse();
630     }
631 
632     @Test
testShouldDismissStandardWarning()633     public void testShouldDismissStandardWarning() {
634         mPowerUI.start();
635         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
636         state.mIsHybrid = false;
637         BatteryStateSnapshot lastState = state.get();
638 
639         // should dismiss if battery saver
640         state.mIsPowerSaver = true;
641         boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
642         assertThat(shouldDismiss).isTrue();
643 
644         state.mIsPowerSaver = false;
645         // should dismiss if plugged
646         state.mPlugged = true;
647         shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
648         assertThat(shouldDismiss).isTrue();
649 
650         state.mPlugged = false;
651         // should dismiss if bucket 0 -> 1
652         state.mBucket = 0;
653         lastState = state.get();
654         state.mBucket = 1;
655         shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
656         assertThat(shouldDismiss).isTrue();
657 
658         // shouldn't dismiss if bucket -1 -> 0
659         state.mBucket = -1;
660         lastState = state.get();
661         state.mBucket = 0;
662         shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
663         assertThat(shouldDismiss).isFalse();
664 
665         // should dismiss if powersaver & bucket 0 -> 1
666         state.mIsPowerSaver = true;
667         state.mBucket = 0;
668         lastState = state.get();
669         state.mBucket = 1;
670         shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
671         assertThat(shouldDismiss).isTrue();
672     }
673 
getEmergencyStatusTemp(int type, String name)674     private Temperature getEmergencyStatusTemp(int type, String name) {
675         final float value = 65;
676         return new Temperature(value, type, name, Temperature.THROTTLING_EMERGENCY);
677     }
678 
getCriticalStatusTemp(int type, String name)679     private Temperature getCriticalStatusTemp(int type, String name) {
680         final float value = 60;
681         return new Temperature(value, type, name, Temperature.THROTTLING_CRITICAL);
682     }
683 
createPowerUi()684     private void createPowerUi() {
685         mPowerUI = new PowerUI(
686                 mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
687                 mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
688                 mUserTracker);
689         mPowerUI.mThermalService = mThermalServiceMock;
690     }
691 
692     /**
693      * A simple wrapper class that sets values by default and makes them not final to improve
694      * test clarity.
695      */
696     private class BatteryStateSnapshotWrapper {
697         public int mBatteryLevel = 100;
698         public boolean mIsPowerSaver = false;
699         public boolean mPlugged = false;
700         public long mSevereThresholdMillis = Duration.ofHours(1).toMillis();
701         public long mLowThresholdMillis = Duration.ofHours(3).toMillis();
702         public int mSevereLevelThreshold = 5;
703         public int mLowLevelThreshold = 15;
704         public int mBucket = 1;
705         public int mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
706         public long mTimeRemainingMillis = Duration.ofHours(24).toMillis();
707         public boolean mIsBasedOnUsage = true;
708         public boolean mIsHybrid = true;
709         public boolean mIsLowLevelWarningEnabled = true;
710         private long mAverageTimeToDischargeMillis = Duration.ofHours(24).toMillis();
711 
get()712         public BatteryStateSnapshot get() {
713             if (mIsHybrid) {
714                 return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
715                         mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold,
716                         mTimeRemainingMillis, mAverageTimeToDischargeMillis, mSevereThresholdMillis,
717                         mLowThresholdMillis, mIsBasedOnUsage, mIsLowLevelWarningEnabled);
718             } else {
719                 return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
720                         mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold);
721             }
722         }
723     }
724 }
725