• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.reset;
28 import static org.mockito.Mockito.timeout;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.Context;
33 import android.hardware.thermal.V2_0.TemperatureThreshold;
34 import android.hardware.thermal.V2_0.ThrottlingSeverity;
35 import android.os.CoolingDevice;
36 import android.os.IBinder;
37 import android.os.IPowerManager;
38 import android.os.IThermalEventListener;
39 import android.os.IThermalService;
40 import android.os.IThermalStatusListener;
41 import android.os.PowerManager;
42 import android.os.RemoteException;
43 import android.os.Temperature;
44 
45 import androidx.test.filters.SmallTest;
46 import androidx.test.runner.AndroidJUnit4;
47 
48 import com.android.server.SystemService;
49 import com.android.server.power.ThermalManagerService.ThermalHalWrapper;
50 
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.ArgumentCaptor;
55 import org.mockito.Mock;
56 import org.mockito.MockitoAnnotations;
57 
58 import java.io.PrintWriter;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashSet;
62 import java.util.List;
63 
64 /**
65  * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server
66  * /power/ThermalManagerServiceTest.java
67  */
68 @SmallTest
69 @RunWith(AndroidJUnit4.class)
70 public class ThermalManagerServiceTest {
71     private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
72     private ThermalManagerService mService;
73     private ThermalHalFake mFakeHal;
74     private PowerManager mPowerManager;
75     @Mock
76     private Context mContext;
77     @Mock
78     private IPowerManager mIPowerManagerMock;
79     @Mock
80     private IThermalService mIThermalServiceMock;
81     @Mock
82     private IThermalEventListener mEventListener1;
83     @Mock
84     private IThermalEventListener mEventListener2;
85     @Mock
86     private IThermalStatusListener mStatusListener1;
87     @Mock
88     private IThermalStatusListener mStatusListener2;
89 
90     /**
91      * Fake Hal class.
92      */
93     private class ThermalHalFake extends ThermalHalWrapper {
94         private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
95         private ArrayList<Temperature> mTemperatureList = new ArrayList<>();
96         private ArrayList<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
97         private ArrayList<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
98 
99         private Temperature mSkin1 = new Temperature(0, Temperature.TYPE_SKIN, "skin1",
100                 INIT_STATUS);
101         private Temperature mSkin2 = new Temperature(0, Temperature.TYPE_SKIN, "skin2",
102                 INIT_STATUS);
103         private Temperature mBattery = new Temperature(0, Temperature.TYPE_BATTERY, "batt",
104                 INIT_STATUS);
105         private Temperature mUsbPort = new Temperature(0, Temperature.TYPE_USB_PORT, "usbport",
106                 INIT_STATUS);
107         private CoolingDevice mCpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "cpu");
108         private CoolingDevice mGpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "gpu");
109 
initializeThresholds()110         private ArrayList<TemperatureThreshold> initializeThresholds() {
111             ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
112 
113             TemperatureThreshold skinThreshold = new TemperatureThreshold();
114             skinThreshold.type = Temperature.TYPE_SKIN;
115             skinThreshold.name = "skin1";
116             skinThreshold.hotThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/];
117             for (int i = 0; i < skinThreshold.hotThrottlingThresholds.length; ++i) {
118                 // Sets NONE to 25.0f, SEVERE to 40.0f, and SHUTDOWN to 55.0f
119                 skinThreshold.hotThrottlingThresholds[i] = 25.0f + 5.0f * i;
120             }
121             thresholds.add(skinThreshold);
122 
123             TemperatureThreshold cpuThreshold = new TemperatureThreshold();
124             cpuThreshold.type = Temperature.TYPE_CPU;
125             cpuThreshold.name = "cpu";
126             cpuThreshold.hotThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/];
127             for (int i = 0; i < cpuThreshold.hotThrottlingThresholds.length; ++i) {
128                 if (i == ThrottlingSeverity.SEVERE) {
129                     cpuThreshold.hotThrottlingThresholds[i] = 95.0f;
130                 } else {
131                     cpuThreshold.hotThrottlingThresholds[i] = Float.NaN;
132                 }
133             }
134             thresholds.add(cpuThreshold);
135 
136             return thresholds;
137         }
138 
ThermalHalFake()139         ThermalHalFake() {
140             mTemperatureList.add(mSkin1);
141             mTemperatureList.add(mSkin2);
142             mTemperatureList.add(mBattery);
143             mTemperatureList.add(mUsbPort);
144             mCoolingDeviceList.add(mCpu);
145             mCoolingDeviceList.add(mGpu);
146         }
147 
148         @Override
getCurrentTemperatures(boolean shouldFilter, int type)149         protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
150             List<Temperature> ret = new ArrayList<>();
151             for (Temperature temperature : mTemperatureList) {
152                 if (shouldFilter && type != temperature.getType()) {
153                     continue;
154                 }
155                 ret.add(temperature);
156             }
157             return ret;
158         }
159 
160         @Override
getCurrentCoolingDevices(boolean shouldFilter, int type)161         protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter, int type) {
162             List<CoolingDevice> ret = new ArrayList<>();
163             for (CoolingDevice cdev : mCoolingDeviceList) {
164                 if (shouldFilter && type != cdev.getType()) {
165                     continue;
166                 }
167                 ret.add(cdev);
168             }
169             return ret;
170         }
171 
172         @Override
getTemperatureThresholds(boolean shouldFilter, int type)173         protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
174                 int type) {
175             List<TemperatureThreshold> ret = new ArrayList<>();
176             for (TemperatureThreshold threshold : mTemperatureThresholdList) {
177                 if (shouldFilter && type != threshold.type) {
178                     continue;
179                 }
180                 ret.add(threshold);
181             }
182             return ret;
183         }
184 
185         @Override
connectToHal()186         protected boolean connectToHal() {
187             return true;
188         }
189 
190         @Override
dump(PrintWriter pw, String prefix)191         protected void dump(PrintWriter pw, String prefix) {
192             return;
193         }
194     }
195 
assertListEqualsIgnoringOrder(List<?> actual, List<?> expected)196     private void assertListEqualsIgnoringOrder(List<?> actual, List<?> expected) {
197         HashSet<?> actualSet = new HashSet<>(actual);
198         HashSet<?> expectedSet = new HashSet<>(expected);
199         assertEquals(expectedSet, actualSet);
200     }
201 
202     @Before
setUp()203     public void setUp() throws RemoteException {
204         MockitoAnnotations.initMocks(this);
205         mFakeHal = new ThermalHalFake();
206         mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock, null);
207         when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
208         when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
209         resetListenerMock();
210         mService = new ThermalManagerService(mContext, mFakeHal);
211         // Register callbacks before AMS ready and no callback sent
212         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
213         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
214         assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
215                 Temperature.TYPE_SKIN));
216         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
217         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
218                 .times(0)).notifyThrottling(any(Temperature.class));
219         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
220                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
221         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
222                 .times(0)).notifyThrottling(any(Temperature.class));
223         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
224                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
225         resetListenerMock();
226         mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
227         ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
228         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
229                 .times(4)).notifyThrottling(captor.capture());
230         assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues());
231         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
232                 .times(0)).onStatusChange(Temperature.THROTTLING_NONE);
233         captor = ArgumentCaptor.forClass(Temperature.class);
234         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
235                 .times(2)).notifyThrottling(captor.capture());
236         assertListEqualsIgnoringOrder(
237                 new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)),
238                 captor.getAllValues());
239         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
240                 .times(0)).onStatusChange(Temperature.THROTTLING_NONE);
241     }
242 
resetListenerMock()243     private void resetListenerMock() {
244         reset(mEventListener1);
245         reset(mStatusListener1);
246         reset(mEventListener2);
247         reset(mStatusListener2);
248         doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
249         doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
250         doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
251         doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
252     }
253 
254     @Test
testRegister()255     public void testRegister() throws RemoteException {
256         resetListenerMock();
257         // Register callbacks and verify they are called
258         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
259         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
260         ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
261         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
262                 .times(4)).notifyThrottling(captor.capture());
263         assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues());
264         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
265                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
266         // Register new callbacks and verify old ones are not called (remained same) while new
267         // ones are called
268         assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
269                 Temperature.TYPE_SKIN));
270         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
271         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
272                 .times(4)).notifyThrottling(any(Temperature.class));
273         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
274                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
275         captor = ArgumentCaptor.forClass(Temperature.class);
276         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
277                 .times(2)).notifyThrottling(captor.capture());
278         assertListEqualsIgnoringOrder(
279                 new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)),
280                 captor.getAllValues());
281         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
282                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
283     }
284 
285     @Test
testNotify()286     public void testNotify() throws RemoteException {
287         int status = Temperature.THROTTLING_SEVERE;
288         // Should only notify event not status
289         Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
290         mFakeHal.mCallback.onValues(newBattery);
291         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
292                 .times(1)).notifyThrottling(newBattery);
293         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
294                 .times(0)).onStatusChange(anyInt());
295         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
296                 .times(0)).notifyThrottling(newBattery);
297         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
298                 .times(0)).onStatusChange(anyInt());
299         resetListenerMock();
300         // Notify both event and status
301         Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
302         mFakeHal.mCallback.onValues(newSkin);
303         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
304                 .times(1)).notifyThrottling(newSkin);
305         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
306                 .times(1)).onStatusChange(status);
307         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
308                 .times(1)).notifyThrottling(newSkin);
309         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
310                 .times(1)).onStatusChange(status);
311         resetListenerMock();
312         // Back to None, should only notify event not status
313         status = Temperature.THROTTLING_NONE;
314         newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
315         mFakeHal.mCallback.onValues(newBattery);
316         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
317                 .times(1)).notifyThrottling(newBattery);
318         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
319                 .times(0)).onStatusChange(anyInt());
320         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
321                 .times(0)).notifyThrottling(newBattery);
322         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
323                 .times(0)).onStatusChange(anyInt());
324         resetListenerMock();
325         // Should also notify status
326         newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
327         mFakeHal.mCallback.onValues(newSkin);
328         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
329                 .times(1)).notifyThrottling(newSkin);
330         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
331                 .times(1)).onStatusChange(status);
332         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
333                 .times(1)).notifyThrottling(newSkin);
334         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
335                 .times(1)).onStatusChange(status);
336     }
337 
338     @Test
testGetCurrentTemperatures()339     public void testGetCurrentTemperatures() throws RemoteException {
340         assertListEqualsIgnoringOrder(mFakeHal.getCurrentTemperatures(false, 0),
341                 Arrays.asList(mService.mService.getCurrentTemperatures()));
342         assertListEqualsIgnoringOrder(
343                 mFakeHal.getCurrentTemperatures(true, Temperature.TYPE_SKIN),
344                 Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
345                         Temperature.TYPE_SKIN)));
346     }
347 
348     @Test
testGetCurrentStatus()349     public void testGetCurrentStatus() throws RemoteException {
350         int status = Temperature.THROTTLING_SEVERE;
351         Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
352         mFakeHal.mCallback.onValues(newSkin);
353         assertEquals(status, mService.mService.getCurrentThermalStatus());
354         int battStatus = Temperature.THROTTLING_EMERGENCY;
355         Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", battStatus);
356         assertEquals(status, mService.mService.getCurrentThermalStatus());
357     }
358 
359     @Test
testThermalShutdown()360     public void testThermalShutdown() throws RemoteException {
361         int status = Temperature.THROTTLING_SHUTDOWN;
362         Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
363         mFakeHal.mCallback.onValues(newSkin);
364         verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
365                 .times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
366         Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", status);
367         mFakeHal.mCallback.onValues(newBattery);
368         verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
369                 .times(1)).shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
370     }
371 
372     @Test
testNoHal()373     public void testNoHal() throws RemoteException {
374         mService = new ThermalManagerService(mContext);
375         // Do no call onActivityManagerReady to skip connect HAL
376         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
377         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
378         assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
379         assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
380         assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperatures()).size());
381         assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
382                         Temperature.TYPE_SKIN)).size());
383         assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
384     }
385 
386     @Test
testGetCurrentCoolingDevices()387     public void testGetCurrentCoolingDevices() throws RemoteException {
388         assertListEqualsIgnoringOrder(mFakeHal.getCurrentCoolingDevices(false, 0),
389                 Arrays.asList(mService.mService.getCurrentCoolingDevices()));
390         assertListEqualsIgnoringOrder(
391                 mFakeHal.getCurrentCoolingDevices(false, CoolingDevice.TYPE_BATTERY),
392                 Arrays.asList(mService.mService.getCurrentCoolingDevices()));
393         assertListEqualsIgnoringOrder(
394                 mFakeHal.getCurrentCoolingDevices(true, CoolingDevice.TYPE_CPU),
395                 Arrays.asList(mService.mService.getCurrentCoolingDevicesWithType(
396                         CoolingDevice.TYPE_CPU)));
397     }
398 
399     @Test
testTemperatureWatcherUpdateSevereThresholds()400     public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
401         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
402         watcher.mSevereThresholds.erase();
403         watcher.updateSevereThresholds();
404         assertEquals(1, watcher.mSevereThresholds.size());
405         assertEquals("skin1", watcher.mSevereThresholds.keyAt(0));
406         Float threshold = watcher.mSevereThresholds.get("skin1");
407         assertNotNull(threshold);
408         assertEquals(40.0f, threshold, 0.0f);
409     }
410 
411     @Test
testTemperatureWatcherGetSlopeOf()412     public void testTemperatureWatcherGetSlopeOf() throws RemoteException {
413         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
414         List<ThermalManagerService.TemperatureWatcher.Sample> samples = new ArrayList<>();
415         for (int i = 0; i < 30; ++i) {
416             samples.add(watcher.createSampleForTesting(i, (float) (i / 2 * 2)));
417         }
418         assertEquals(1.0f, watcher.getSlopeOf(samples), 0.01f);
419     }
420 
421     @Test
testTemperatureWatcherNormalizeTemperature()422     public void testTemperatureWatcherNormalizeTemperature() throws RemoteException {
423         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
424         assertEquals(0.5f, watcher.normalizeTemperature(25.0f, 40.0f), 0.0f);
425 
426         // Temperatures more than 30 degrees below the SEVERE threshold should be clamped to 0.0f
427         assertEquals(0.0f, watcher.normalizeTemperature(0.0f, 40.0f), 0.0f);
428 
429         // Temperatures above the SEVERE threshold should not be clamped
430         assertEquals(2.0f, watcher.normalizeTemperature(70.0f, 40.0f), 0.0f);
431     }
432 
433     @Test
testTemperatureWatcherGetForecast()434     public void testTemperatureWatcherGetForecast() throws RemoteException {
435         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
436 
437         ArrayList<ThermalManagerService.TemperatureWatcher.Sample> samples = new ArrayList<>();
438 
439         // Add a single sample
440         samples.add(watcher.createSampleForTesting(0, 25.0f));
441         watcher.mSamples.put("skin1", samples);
442 
443         // Because there are not enough samples to compute the linear regression,
444         // no matter how far ahead we forecast, we should receive the same value
445         assertEquals(0.5f, watcher.getForecast(0), 0.0f);
446         assertEquals(0.5f, watcher.getForecast(5), 0.0f);
447 
448         // Add some time-series data
449         for (int i = 1; i < 20; ++i) {
450             samples.add(0, watcher.createSampleForTesting(1000 * i, 25.0f + 0.5f * i));
451         }
452 
453         // Now the forecast should vary depending on how far ahead we are trying to predict
454         assertEquals(0.9f, watcher.getForecast(4), 0.02f);
455         assertEquals(1.0f, watcher.getForecast(10), 0.02f);
456 
457         // If there are no thresholds, then we shouldn't receive a headroom value
458         watcher.mSevereThresholds.erase();
459         assertTrue(Float.isNaN(watcher.getForecast(0)));
460     }
461 
462     @Test
testTemperatureWatcherGetForecastUpdate()463     public void testTemperatureWatcherGetForecastUpdate() throws Exception {
464         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
465 
466         // Reduce the inactivity threshold to speed up testing
467         watcher.mInactivityThresholdMillis = 2000;
468 
469         // Make sure mSamples is empty before updateTemperature
470         assertTrue(isWatcherSamplesEmpty(watcher));
471 
472         // Call getForecast once to trigger updateTemperature
473         watcher.getForecast(0);
474 
475         // After 1 second, the samples should be updated
476         Thread.sleep(1000);
477         assertFalse(isWatcherSamplesEmpty(watcher));
478 
479         // After mInactivityThresholdMillis, the samples should be cleared
480         Thread.sleep(watcher.mInactivityThresholdMillis);
481         assertTrue(isWatcherSamplesEmpty(watcher));
482     }
483 
484     // Helper function to hold mSamples lock, avoid GuardedBy lint errors
isWatcherSamplesEmpty(ThermalManagerService.TemperatureWatcher watcher)485     private boolean isWatcherSamplesEmpty(ThermalManagerService.TemperatureWatcher watcher) {
486         synchronized (watcher.mSamples) {
487             return watcher.mSamples.isEmpty();
488         }
489     }
490 }
491