Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bda4276d authored by Xiang Wang's avatar Xiang Wang Committed by Android (Google) Code Review
Browse files

Merge "Add support for using HAL forecast for thermal headroom" into main

parents 4fff241a eea3ac5b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -109,6 +109,14 @@ flag {
    is_exported: true
}

flag {
    name: "allow_thermal_hal_skin_forecast"
    is_exported: true
    namespace: "game"
    description: "Enable thermal HAL skin temperature forecast to be used by headroom API"
    bug: "383211885"
}

flag {
    name: "allow_thermal_headroom_thresholds"
    is_exported: true
+202 −111
Original line number Diff line number Diff line
@@ -155,6 +155,9 @@ public class ThermalManagerService extends SystemService {
    @VisibleForTesting
    final TemperatureWatcher mTemperatureWatcher;

    @VisibleForTesting
    final AtomicBoolean mIsHalSkinForecastSupported = new AtomicBoolean(false);

    private final ThermalHalWrapper.WrapperThermalChangedCallback mWrapperCallback =
            new ThermalHalWrapper.WrapperThermalChangedCallback() {
                @Override
@@ -254,6 +257,18 @@ public class ThermalManagerService extends SystemService {
            }
            onTemperatureMapChangedLocked();
            mTemperatureWatcher.getAndUpdateThresholds();
            // we only check forecast if a single SKIN sensor threshold is reported
            synchronized (mTemperatureWatcher.mSamples) {
                if (mTemperatureWatcher.mSevereThresholds.size() == 1) {
                    try {
                        mIsHalSkinForecastSupported.set(
                                Flags.allowThermalHalSkinForecast()
                                        && !Float.isNaN(mHalWrapper.forecastSkinTemperature(10)));
                    } catch (UnsupportedOperationException e) {
                        Slog.i(TAG, "Thermal HAL does not support forecastSkinTemperature");
                    }
                }
            }
            mHalReady.set(true);
        }
    }
@@ -1092,6 +1107,8 @@ public class ThermalManagerService extends SystemService {
        protected abstract List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
                int type);

        protected abstract float forecastSkinTemperature(int forecastSeconds);

        protected abstract boolean connectToHal();

        protected abstract void dump(PrintWriter pw, String prefix);
@@ -1124,8 +1141,16 @@ public class ThermalManagerService extends SystemService {
    @VisibleForTesting
    static class ThermalHalAidlWrapper extends ThermalHalWrapper implements IBinder.DeathRecipient {
        /* Proxy object for the Thermal HAL AIDL service. */

        @GuardedBy("mHalLock")
        private IThermal mInstance = null;

        private IThermal getHalInstance() {
            synchronized (mHalLock) {
                return mInstance;
            }
        }

        /** Callback for Thermal HAL AIDL. */
        private final IThermalChangedCallback mThermalCallbackAidl =
                new IThermalChangedCallback.Stub() {
@@ -1169,15 +1194,15 @@ public class ThermalManagerService extends SystemService {
        @Override
        protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
                int type) {
            synchronized (mHalLock) {
            final IThermal instance = getHalInstance();
            final List<Temperature> ret = new ArrayList<>();
                if (mInstance == null) {
            if (instance == null) {
                return ret;
            }
            try {
                final android.hardware.thermal.Temperature[] halRet =
                            shouldFilter ? mInstance.getTemperaturesWithType(type)
                                    : mInstance.getTemperatures();
                        shouldFilter ? instance.getTemperaturesWithType(type)
                                : instance.getTemperatures();
                if (halRet == null) {
                    return ret;
                }
@@ -1196,24 +1221,25 @@ public class ThermalManagerService extends SystemService {
                Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
            } catch (RemoteException e) {
                Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting", e);
                    connectToHal();
                synchronized (mHalLock) {
                    connectToHalIfNeededLocked(instance);
                }
                return ret;
            }
            return ret;
        }

        @Override
        protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
                int type) {
            synchronized (mHalLock) {
            final IThermal instance = getHalInstance();
            final List<CoolingDevice> ret = new ArrayList<>();
                if (mInstance == null) {
            if (instance == null) {
                return ret;
            }
            try {
                final android.hardware.thermal.CoolingDevice[] halRet = shouldFilter
                            ? mInstance.getCoolingDevicesWithType(type)
                            : mInstance.getCoolingDevices();
                        ? instance.getCoolingDevicesWithType(type)
                        : instance.getCoolingDevices();
                if (halRet == null) {
                    return ret;
                }
@@ -1231,25 +1257,26 @@ public class ThermalManagerService extends SystemService {
                Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
            } catch (RemoteException e) {
                Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting", e);
                    connectToHal();
                synchronized (mHalLock) {
                    connectToHalIfNeededLocked(instance);
                }
                return ret;
            }
            return ret;
        }

        @Override
        @NonNull
        protected List<TemperatureThreshold> getTemperatureThresholds(
                boolean shouldFilter, int type) {
            synchronized (mHalLock) {
            final IThermal instance = getHalInstance();
            final List<TemperatureThreshold> ret = new ArrayList<>();
                if (mInstance == null) {
            if (instance == null) {
                return ret;
            }
            try {
                final TemperatureThreshold[] halRet =
                            shouldFilter ? mInstance.getTemperatureThresholdsWithType(type)
                                    : mInstance.getTemperatureThresholds();
                        shouldFilter ? instance.getTemperatureThresholdsWithType(type)
                                : instance.getTemperatureThresholds();
                if (halRet == null) {
                    return ret;
                }
@@ -1262,25 +1289,58 @@ public class ThermalManagerService extends SystemService {
                Slog.e(TAG, "Couldn't getTemperatureThresholds due to invalid status", e);
            } catch (RemoteException e) {
                Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
                    connectToHal();
                synchronized (mHalLock) {
                    connectToHalIfNeededLocked(instance);
                }
            }
            return ret;
        }

        @Override
        protected float forecastSkinTemperature(int forecastSeconds) {
            final IThermal instance = getHalInstance();
            if (instance == null) {
                return Float.NaN;
            }
            try {
                return instance.forecastSkinTemperature(forecastSeconds);
            } catch (RemoteException e) {
                Slog.e(TAG, "Couldn't forecastSkinTemperature, reconnecting...", e);
                synchronized (mHalLock) {
                    connectToHalIfNeededLocked(instance);
                }
            }
            return Float.NaN;
        }

        @Override
        protected boolean connectToHal() {
            synchronized (mHalLock) {
                return connectToHalIfNeededLocked(mInstance);
            }
        }

        @GuardedBy("mHalLock")
        protected boolean connectToHalIfNeededLocked(IThermal instance) {
            if (instance != mInstance) {
                // instance has been updated since last used
                return true;
            }
            IBinder binder = Binder.allowBlocking(ServiceManager.waitForDeclaredService(
                    IThermal.DESCRIPTOR + "/default"));
                initProxyAndRegisterCallback(binder);
            }
            initProxyAndRegisterCallbackLocked(binder);
            return mInstance != null;
        }

        @VisibleForTesting
        void initProxyAndRegisterCallback(IBinder binder) {
            synchronized (mHalLock) {
                initProxyAndRegisterCallbackLocked(binder);
            }
        }

        @GuardedBy("mHalLock")
        protected void initProxyAndRegisterCallbackLocked(IBinder binder) {
            if (binder != null) {
                mInstance = IThermal.Stub.asInterface(binder);
                try {
@@ -1298,14 +1358,6 @@ public class ThermalManagerService extends SystemService {
                        connectToHal();
                        return;
                    }
                        registerThermalChangedCallback();
                    }
                }
            }
        }

        @VisibleForTesting
        void registerThermalChangedCallback() {
                    try {
                        mInstance.registerThermalChangedCallback(mThermalCallbackAidl);
                    } catch (IllegalArgumentException | IllegalStateException e) {
@@ -1316,6 +1368,8 @@ public class ThermalManagerService extends SystemService {
                        connectToHal();
                    }
                }
            }
        }

        @Override
        protected void dump(PrintWriter pw, String prefix) {
@@ -1444,6 +1498,11 @@ public class ThermalManagerService extends SystemService {
            }
        }

        @Override
        protected float forecastSkinTemperature(int forecastSeconds) {
            throw new UnsupportedOperationException("Not supported in Thermal HAL 1.0");
        }

        @Override
        protected void dump(PrintWriter pw, String prefix) {
            synchronized (mHalLock) {
@@ -1582,6 +1641,11 @@ public class ThermalManagerService extends SystemService {
            }
        }

        @Override
        protected float forecastSkinTemperature(int forecastSeconds) {
            throw new UnsupportedOperationException("Not supported in Thermal HAL 1.1");
        }

        @Override
        protected void dump(PrintWriter pw, String prefix) {
            synchronized (mHalLock) {
@@ -1748,6 +1812,11 @@ public class ThermalManagerService extends SystemService {
            }
        }

        @Override
        protected float forecastSkinTemperature(int forecastSeconds) {
            throw new UnsupportedOperationException("Not supported in Thermal HAL 2.0");
        }

        @Override
        protected void dump(PrintWriter pw, String prefix) {
            synchronized (mHalLock) {
@@ -1976,6 +2045,39 @@ public class ThermalManagerService extends SystemService {
        private static final int MINIMUM_SAMPLE_COUNT = 3;

        float getForecast(int forecastSeconds) {
            synchronized (mSamples) {
                // If we don't have any thresholds, we can't normalize the temperatures,
                // so return early
                if (mSevereThresholds.isEmpty()) {
                    Slog.e(TAG, "No temperature thresholds found");
                    FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                            Binder.getCallingUid(),
                            THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD,
                            Float.NaN, forecastSeconds);
                    return Float.NaN;
                }
            }
            if (mIsHalSkinForecastSupported.get()) {
                float threshold = -1f;
                synchronized (mSamples) {
                    // we only do forecast if a single SKIN sensor threshold is reported
                    if (mSevereThresholds.size() == 1) {
                        threshold = mSevereThresholds.valueAt(0);
                    }
                }
                if (threshold > 0) {
                    try {
                        final float forecastTemperature =
                                mHalWrapper.forecastSkinTemperature(forecastSeconds);
                        return normalizeTemperature(forecastTemperature, threshold);
                    } catch (UnsupportedOperationException e) {
                        Slog.wtf(TAG, "forecastSkinTemperature returns unsupported");
                    } catch (Exception e) {
                        Slog.e(TAG, "forecastSkinTemperature fails");
                    }
                    return Float.NaN;
                }
            }
            synchronized (mSamples) {
                mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
                if (mSamples.isEmpty()) {
@@ -1993,17 +2095,6 @@ public class ThermalManagerService extends SystemService {
                    return Float.NaN;
                }

                // If we don't have any thresholds, we can't normalize the temperatures,
                // so return early
                if (mSevereThresholds.isEmpty()) {
                    Slog.e(TAG, "No temperature thresholds found");
                    FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                            Binder.getCallingUid(),
                            THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD,
                            Float.NaN, forecastSeconds);
                    return Float.NaN;
                }

                if (mCachedHeadrooms.contains(forecastSeconds)) {
                    // TODO(b/360486877): replace with metrics
                    Slog.d(TAG,
+10 −0
Original line number Diff line number Diff line
@@ -573,4 +573,14 @@ public class ThermalManagerServiceMockingTest {
        assertNotNull(ret);
        assertEquals(0, ret.size());
    }

    @Test
    public void forecastSkinTemperature() throws RemoteException {
        Mockito.when(mAidlHalMock.forecastSkinTemperature(Mockito.anyInt())).thenReturn(
                0.55f
        );
        float forecast = mAidlWrapper.forecastSkinTemperature(10);
        Mockito.verify(mAidlHalMock, Mockito.times(1)).forecastSkinTemperature(10);
        assertEquals(0.55f, forecast, 0.01f);
    }
}
+118 −6
Original line number Diff line number Diff line
@@ -118,7 +118,6 @@ public class ThermalManagerServiceTest {
    private class ThermalHalFake extends ThermalHalWrapper {
        private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
        private List<Temperature> mTemperatureList = new ArrayList<>();
        private List<Temperature> mOverrideTemperatures = null;
        private List<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
        private List<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();

@@ -132,6 +131,9 @@ public class ThermalManagerServiceTest {
                INIT_STATUS);
        private CoolingDevice mCpu = new CoolingDevice(40, CoolingDevice.TYPE_BATTERY, "cpu");
        private CoolingDevice mGpu = new CoolingDevice(43, CoolingDevice.TYPE_BATTERY, "gpu");
        private Map<Integer, Float> mForecastSkinTemperatures = null;
        private int mForecastSkinTemperaturesCalled = 0;
        private boolean mForecastSkinTemperaturesError = false;

        private List<TemperatureThreshold> initializeThresholds() {
            ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
@@ -173,12 +175,17 @@ public class ThermalManagerServiceTest {
            mCoolingDeviceList.add(mGpu);
        }

        void setOverrideTemperatures(List<Temperature> temperatures) {
            mOverrideTemperatures = temperatures;
        void enableForecastSkinTemperature() {
            mForecastSkinTemperatures = Map.of(0, 22.0f, 10, 25.0f, 20, 28.0f,
                    30, 31.0f, 40, 34.0f, 50, 37.0f, 60, 40.0f);
        }

        void resetOverrideTemperatures() {
            mOverrideTemperatures = null;
        void disableForecastSkinTemperature() {
            mForecastSkinTemperatures = null;
        }

        void failForecastSkinTemperature() {
            mForecastSkinTemperaturesError = true;
        }

        @Override
@@ -218,6 +225,18 @@ public class ThermalManagerServiceTest {
            return ret;
        }

        @Override
        protected float forecastSkinTemperature(int forecastSeconds) {
            mForecastSkinTemperaturesCalled++;
            if (mForecastSkinTemperaturesError) {
                throw new RuntimeException();
            }
            if (mForecastSkinTemperatures == null) {
                throw new UnsupportedOperationException();
            }
            return mForecastSkinTemperatures.get(forecastSeconds);
        }

        @Override
        protected boolean connectToHal() {
            return true;
@@ -388,7 +407,7 @@ public class ThermalManagerServiceTest {
        Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
        resetListenerMock();
        int status = Temperature.THROTTLING_SEVERE;
        mFakeHal.setOverrideTemperatures(new ArrayList<>());
        mFakeHal.mTemperatureList = new ArrayList<>();

        // Should not notify on non-skin type
        Temperature newBattery = new Temperature(37, Temperature.TYPE_BATTERY, "batt", status);
@@ -517,6 +536,99 @@ public class ThermalManagerServiceTest {
                ThermalManagerService.MAX_FORECAST_SEC + 1)));
    }

    @Test
    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST})
    public void testGetThermalHeadroom_halForecast() throws RemoteException {
        mFakeHal.mForecastSkinTemperaturesCalled = 0;
        mFakeHal.enableForecastSkinTemperature();
        mService = new ThermalManagerService(mContext, mFakeHal);
        mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
        assertTrue(mService.mIsHalSkinForecastSupported.get());
        assertEquals(1, mFakeHal.mForecastSkinTemperaturesCalled);
        mFakeHal.mForecastSkinTemperaturesCalled = 0;

        assertEquals(1.0f, mService.mService.getThermalHeadroom(60), 0.01f);
        assertEquals(0.9f, mService.mService.getThermalHeadroom(50), 0.01f);
        assertEquals(0.8f, mService.mService.getThermalHeadroom(40), 0.01f);
        assertEquals(0.7f, mService.mService.getThermalHeadroom(30), 0.01f);
        assertEquals(0.6f, mService.mService.getThermalHeadroom(20), 0.01f);
        assertEquals(0.5f, mService.mService.getThermalHeadroom(10), 0.01f);
        assertEquals(0.4f, mService.mService.getThermalHeadroom(0), 0.01f);
        assertEquals(7, mFakeHal.mForecastSkinTemperaturesCalled);
    }

    @Test
    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST})
    public void testGetThermalHeadroom_halForecast_disabledOnMultiThresholds()
            throws RemoteException {
        mFakeHal.mForecastSkinTemperaturesCalled = 0;
        List<TemperatureThreshold> thresholds = mFakeHal.initializeThresholds();
        TemperatureThreshold skinThreshold = new TemperatureThreshold();
        skinThreshold.type = Temperature.TYPE_SKIN;
        skinThreshold.name = "skin2";
        skinThreshold.hotThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/];
        skinThreshold.coldThrottlingThresholds = new float[7 /*ThrottlingSeverity#len*/];
        for (int i = 0; i < skinThreshold.hotThrottlingThresholds.length; ++i) {
            // Sets NONE to 45.0f, SEVERE to 60.0f, and SHUTDOWN to 75.0f
            skinThreshold.hotThrottlingThresholds[i] = 45.0f + 5.0f * i;
        }
        thresholds.add(skinThreshold);
        mFakeHal.mTemperatureThresholdList = thresholds;
        mFakeHal.enableForecastSkinTemperature();
        mService = new ThermalManagerService(mContext, mFakeHal);
        mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
        assertFalse("HAL skin forecast should be disabled on multiple SKIN thresholds",
                mService.mIsHalSkinForecastSupported.get());
        mService.mService.getThermalHeadroom(10);
        assertEquals(0, mFakeHal.mForecastSkinTemperaturesCalled);
    }

    @Test
    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST,
            Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK})
    public void testGetThermalHeadroom_halForecast_disabledOnMultiThresholdsCallback()
            throws RemoteException {
        mFakeHal.mForecastSkinTemperaturesCalled = 0;
        mFakeHal.enableForecastSkinTemperature();
        mService = new ThermalManagerService(mContext, mFakeHal);
        mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
        assertTrue(mService.mIsHalSkinForecastSupported.get());
        assertEquals(1, mFakeHal.mForecastSkinTemperaturesCalled);
        mFakeHal.mForecastSkinTemperaturesCalled = 0;

        TemperatureThreshold newThreshold = new TemperatureThreshold();
        newThreshold.name = "skin2";
        newThreshold.type = Temperature.TYPE_SKIN;
        newThreshold.hotThrottlingThresholds = new float[]{
                Float.NaN, 43.0f, 46.0f, 49.0f, Float.NaN, Float.NaN, Float.NaN
        };
        mFakeHal.mCallback.onThresholdChanged(newThreshold);
        mService.mService.getThermalHeadroom(10);
        assertEquals(0, mFakeHal.mForecastSkinTemperaturesCalled);
    }

    @Test
    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST})
    public void testGetThermalHeadroom_halForecast_errorOnHal() throws RemoteException {
        mFakeHal.mForecastSkinTemperaturesCalled = 0;
        mFakeHal.enableForecastSkinTemperature();
        mService = new ThermalManagerService(mContext, mFakeHal);
        mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
        assertTrue(mService.mIsHalSkinForecastSupported.get());
        assertEquals(1, mFakeHal.mForecastSkinTemperaturesCalled);
        mFakeHal.mForecastSkinTemperaturesCalled = 0;

        mFakeHal.disableForecastSkinTemperature();
        assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(10)));
        assertEquals(1, mFakeHal.mForecastSkinTemperaturesCalled);
        mFakeHal.enableForecastSkinTemperature();
        assertFalse(Float.isNaN(mService.mService.getThermalHeadroom(10)));
        assertEquals(2, mFakeHal.mForecastSkinTemperaturesCalled);
        mFakeHal.failForecastSkinTemperature();
        assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(10)));
        assertEquals(3, mFakeHal.mForecastSkinTemperaturesCalled);
    }

    @Test
    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK,
            Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS})