| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.power; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.hardware.thermal.V2_0.TemperatureThreshold; |
| import android.hardware.thermal.V2_0.ThrottlingSeverity; |
| import android.os.CoolingDevice; |
| import android.os.IBinder; |
| import android.os.IPowerManager; |
| import android.os.IThermalEventListener; |
| import android.os.IThermalService; |
| import android.os.IThermalStatusListener; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.Temperature; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.server.SystemService; |
| import com.android.server.power.ThermalManagerService.ThermalHalWrapper; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| |
| /** |
| * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server |
| * /power/ThermalManagerServiceTest.java |
| */ |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| public class ThermalManagerServiceTest { |
| private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000; |
| private ThermalManagerService mService; |
| private ThermalHalFake mFakeHal; |
| private PowerManager mPowerManager; |
| @Mock |
| private Context mContext; |
| @Mock |
| private IPowerManager mIPowerManagerMock; |
| @Mock |
| private IThermalService mIThermalServiceMock; |
| @Mock |
| private IThermalEventListener mEventListener1; |
| @Mock |
| private IThermalEventListener mEventListener2; |
| @Mock |
| private IThermalStatusListener mStatusListener1; |
| @Mock |
| private IThermalStatusListener mStatusListener2; |
| |
| /** |
| * Fake Hal class. |
| */ |
| private class ThermalHalFake extends ThermalHalWrapper { |
| private static final int INIT_STATUS = Temperature.THROTTLING_NONE; |
| private ArrayList<Temperature> mTemperatureList = new ArrayList<>(); |
| private ArrayList<CoolingDevice> mCoolingDeviceList = new ArrayList<>(); |
| private ArrayList<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds(); |
| |
| private Temperature mSkin1 = new Temperature(0, Temperature.TYPE_SKIN, "skin1", |
| INIT_STATUS); |
| private Temperature mSkin2 = new Temperature(0, Temperature.TYPE_SKIN, "skin2", |
| INIT_STATUS); |
| private Temperature mBattery = new Temperature(0, Temperature.TYPE_BATTERY, "batt", |
| INIT_STATUS); |
| private Temperature mUsbPort = new Temperature(0, Temperature.TYPE_USB_PORT, "usbport", |
| INIT_STATUS); |
| private CoolingDevice mCpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "cpu"); |
| private CoolingDevice mGpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "gpu"); |
| |
| private ArrayList<TemperatureThreshold> initializeThresholds() { |
| ArrayList<TemperatureThreshold> thresholds = new ArrayList<>(); |
| |
| TemperatureThreshold skinThreshold = new TemperatureThreshold(); |
| skinThreshold.type = Temperature.TYPE_SKIN; |
| skinThreshold.name = "skin1"; |
| skinThreshold.hotThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/]; |
| for (int i = 0; i < skinThreshold.hotThrottlingThresholds.length; ++i) { |
| // Sets NONE to 25.0f, SEVERE to 40.0f, and SHUTDOWN to 55.0f |
| skinThreshold.hotThrottlingThresholds[i] = 25.0f + 5.0f * i; |
| } |
| thresholds.add(skinThreshold); |
| |
| TemperatureThreshold cpuThreshold = new TemperatureThreshold(); |
| cpuThreshold.type = Temperature.TYPE_CPU; |
| cpuThreshold.name = "cpu"; |
| cpuThreshold.hotThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/]; |
| for (int i = 0; i < cpuThreshold.hotThrottlingThresholds.length; ++i) { |
| if (i == ThrottlingSeverity.SEVERE) { |
| cpuThreshold.hotThrottlingThresholds[i] = 95.0f; |
| } else { |
| cpuThreshold.hotThrottlingThresholds[i] = Float.NaN; |
| } |
| } |
| thresholds.add(cpuThreshold); |
| |
| return thresholds; |
| } |
| |
| ThermalHalFake() { |
| mTemperatureList.add(mSkin1); |
| mTemperatureList.add(mSkin2); |
| mTemperatureList.add(mBattery); |
| mTemperatureList.add(mUsbPort); |
| mCoolingDeviceList.add(mCpu); |
| mCoolingDeviceList.add(mGpu); |
| } |
| |
| @Override |
| protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) { |
| List<Temperature> ret = new ArrayList<>(); |
| for (Temperature temperature : mTemperatureList) { |
| if (shouldFilter && type != temperature.getType()) { |
| continue; |
| } |
| ret.add(temperature); |
| } |
| return ret; |
| } |
| |
| @Override |
| protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter, int type) { |
| List<CoolingDevice> ret = new ArrayList<>(); |
| for (CoolingDevice cdev : mCoolingDeviceList) { |
| if (shouldFilter && type != cdev.getType()) { |
| continue; |
| } |
| ret.add(cdev); |
| } |
| return ret; |
| } |
| |
| @Override |
| protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter, |
| int type) { |
| List<TemperatureThreshold> ret = new ArrayList<>(); |
| for (TemperatureThreshold threshold : mTemperatureThresholdList) { |
| if (shouldFilter && type != threshold.type) { |
| continue; |
| } |
| ret.add(threshold); |
| } |
| return ret; |
| } |
| |
| @Override |
| protected boolean connectToHal() { |
| return true; |
| } |
| |
| @Override |
| protected void dump(PrintWriter pw, String prefix) { |
| return; |
| } |
| } |
| |
| private void assertListEqualsIgnoringOrder(List<?> actual, List<?> expected) { |
| HashSet<?> actualSet = new HashSet<>(actual); |
| HashSet<?> expectedSet = new HashSet<>(expected); |
| assertEquals(expectedSet, actualSet); |
| } |
| |
| @Before |
| public void setUp() throws RemoteException { |
| MockitoAnnotations.initMocks(this); |
| mFakeHal = new ThermalHalFake(); |
| mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock, null); |
| when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE); |
| when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager); |
| resetListenerMock(); |
| mService = new ThermalManagerService(mContext, mFakeHal); |
| // Register callbacks before AMS ready and no callback sent |
| assertTrue(mService.mService.registerThermalEventListener(mEventListener1)); |
| assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1)); |
| assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2, |
| Temperature.TYPE_SKIN)); |
| assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2)); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).notifyThrottling(any(Temperature.class)); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(Temperature.THROTTLING_NONE); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).notifyThrottling(any(Temperature.class)); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(Temperature.THROTTLING_NONE); |
| resetListenerMock(); |
| mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); |
| ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(4)).notifyThrottling(captor.capture()); |
| assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues()); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(Temperature.THROTTLING_NONE); |
| captor = ArgumentCaptor.forClass(Temperature.class); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(2)).notifyThrottling(captor.capture()); |
| assertListEqualsIgnoringOrder( |
| new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)), |
| captor.getAllValues()); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(Temperature.THROTTLING_NONE); |
| } |
| |
| private void resetListenerMock() { |
| reset(mEventListener1); |
| reset(mStatusListener1); |
| reset(mEventListener2); |
| reset(mStatusListener2); |
| doReturn(mock(IBinder.class)).when(mEventListener1).asBinder(); |
| doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder(); |
| doReturn(mock(IBinder.class)).when(mEventListener2).asBinder(); |
| doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder(); |
| } |
| |
| @Test |
| public void testRegister() throws RemoteException { |
| resetListenerMock(); |
| // Register callbacks and verify they are called |
| assertTrue(mService.mService.registerThermalEventListener(mEventListener1)); |
| assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1)); |
| ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(4)).notifyThrottling(captor.capture()); |
| assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues()); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(Temperature.THROTTLING_NONE); |
| // Register new callbacks and verify old ones are not called (remained same) while new |
| // ones are called |
| assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2, |
| Temperature.TYPE_SKIN)); |
| assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2)); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(4)).notifyThrottling(any(Temperature.class)); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(Temperature.THROTTLING_NONE); |
| captor = ArgumentCaptor.forClass(Temperature.class); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(2)).notifyThrottling(captor.capture()); |
| assertListEqualsIgnoringOrder( |
| new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)), |
| captor.getAllValues()); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(Temperature.THROTTLING_NONE); |
| } |
| |
| @Test |
| public void testNotify() throws RemoteException { |
| int status = Temperature.THROTTLING_SEVERE; |
| Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status); |
| mFakeHal.mCallback.onValues(newBattery); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newBattery); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(status); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).notifyThrottling(newBattery); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(status); |
| resetListenerMock(); |
| // Should only notify event not status |
| Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status); |
| mFakeHal.mCallback.onValues(newSkin); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newSkin); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(anyInt()); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newSkin); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(anyInt()); |
| resetListenerMock(); |
| // Back to None, should only notify event not status |
| status = Temperature.THROTTLING_NONE; |
| newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status); |
| mFakeHal.mCallback.onValues(newBattery); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newBattery); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(anyInt()); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).notifyThrottling(newBattery); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(0)).onStatusChange(anyInt()); |
| resetListenerMock(); |
| // Should also notify status |
| newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status); |
| mFakeHal.mCallback.onValues(newSkin); |
| verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newSkin); |
| verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(status); |
| verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).notifyThrottling(newSkin); |
| verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).onStatusChange(status); |
| } |
| |
| @Test |
| public void testGetCurrentTemperatures() throws RemoteException { |
| assertListEqualsIgnoringOrder(mFakeHal.getCurrentTemperatures(false, 0), |
| Arrays.asList(mService.mService.getCurrentTemperatures())); |
| assertListEqualsIgnoringOrder( |
| mFakeHal.getCurrentTemperatures(true, Temperature.TYPE_SKIN), |
| Arrays.asList(mService.mService.getCurrentTemperaturesWithType( |
| Temperature.TYPE_SKIN))); |
| } |
| |
| @Test |
| public void testGetCurrentStatus() throws RemoteException { |
| int status = Temperature.THROTTLING_EMERGENCY; |
| Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status); |
| mFakeHal.mCallback.onValues(newSkin); |
| assertEquals(status, mService.mService.getCurrentThermalStatus()); |
| } |
| |
| @Test |
| public void testThermalShutdown() throws RemoteException { |
| int status = Temperature.THROTTLING_SHUTDOWN; |
| Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status); |
| mFakeHal.mCallback.onValues(newSkin); |
| verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false); |
| Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", status); |
| mFakeHal.mCallback.onValues(newBattery); |
| verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC) |
| .times(1)).shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false); |
| } |
| |
| @Test |
| public void testNoHal() throws RemoteException { |
| mService = new ThermalManagerService(mContext); |
| // Do no call onActivityManagerReady to skip connect HAL |
| assertTrue(mService.mService.registerThermalEventListener(mEventListener1)); |
| assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1)); |
| assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1)); |
| assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1)); |
| assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperatures()).size()); |
| assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType( |
| Temperature.TYPE_SKIN)).size()); |
| assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus()); |
| } |
| |
| @Test |
| public void testGetCurrentCoolingDevices() throws RemoteException { |
| assertListEqualsIgnoringOrder(mFakeHal.getCurrentCoolingDevices(false, 0), |
| Arrays.asList(mService.mService.getCurrentCoolingDevices())); |
| assertListEqualsIgnoringOrder( |
| mFakeHal.getCurrentCoolingDevices(false, CoolingDevice.TYPE_BATTERY), |
| Arrays.asList(mService.mService.getCurrentCoolingDevices())); |
| assertListEqualsIgnoringOrder( |
| mFakeHal.getCurrentCoolingDevices(true, CoolingDevice.TYPE_CPU), |
| Arrays.asList(mService.mService.getCurrentCoolingDevicesWithType( |
| CoolingDevice.TYPE_CPU))); |
| } |
| |
| @Test |
| public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException { |
| ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher; |
| watcher.mSevereThresholds.erase(); |
| watcher.updateSevereThresholds(); |
| assertEquals(1, watcher.mSevereThresholds.size()); |
| assertEquals("skin1", watcher.mSevereThresholds.keyAt(0)); |
| Float threshold = watcher.mSevereThresholds.get("skin1"); |
| assertNotNull(threshold); |
| assertEquals(40.0f, threshold, 0.0f); |
| } |
| |
| @Test |
| public void testTemperatureWatcherGetSlopeOf() throws RemoteException { |
| ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher; |
| List<ThermalManagerService.TemperatureWatcher.Sample> samples = new ArrayList<>(); |
| for (int i = 0; i < 30; ++i) { |
| samples.add(watcher.createSampleForTesting(i, (float) (i / 2 * 2))); |
| } |
| assertEquals(1.0f, watcher.getSlopeOf(samples), 0.01f); |
| } |
| |
| @Test |
| public void testTemperatureWatcherNormalizeTemperature() throws RemoteException { |
| ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher; |
| assertEquals(0.5f, watcher.normalizeTemperature(25.0f, 40.0f), 0.0f); |
| |
| // Temperatures more than 30 degrees below the SEVERE threshold should be clamped to 0.0f |
| assertEquals(0.0f, watcher.normalizeTemperature(0.0f, 40.0f), 0.0f); |
| |
| // Temperatures above the SEVERE threshold should not be clamped |
| assertEquals(2.0f, watcher.normalizeTemperature(70.0f, 40.0f), 0.0f); |
| } |
| |
| @Test |
| public void testTemperatureWatcherGetForecast() throws RemoteException { |
| ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher; |
| |
| ArrayList<ThermalManagerService.TemperatureWatcher.Sample> samples = new ArrayList<>(); |
| |
| // Add a single sample |
| samples.add(watcher.createSampleForTesting(0, 25.0f)); |
| watcher.mSamples.put("skin1", samples); |
| |
| // Because there are not enough samples to compute the linear regression, |
| // no matter how far ahead we forecast, we should receive the same value |
| assertEquals(0.5f, watcher.getForecast(0), 0.0f); |
| assertEquals(0.5f, watcher.getForecast(5), 0.0f); |
| |
| // Add some time-series data |
| for (int i = 1; i < 20; ++i) { |
| samples.add(0, watcher.createSampleForTesting(1000 * i, 25.0f + 0.5f * i)); |
| } |
| |
| // Now the forecast should vary depending on how far ahead we are trying to predict |
| assertEquals(0.9f, watcher.getForecast(4), 0.02f); |
| assertEquals(1.0f, watcher.getForecast(10), 0.02f); |
| |
| // If there are no thresholds, then we shouldn't receive a headroom value |
| watcher.mSevereThresholds.erase(); |
| assertTrue(Float.isNaN(watcher.getForecast(0))); |
| } |
| } |