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