Add PowerManager#getThermalHeadroom
Adds getThermalHeadroom, which allows users to determine how close the device currently is to SEVERE thermal throttling. For intensive applications which can scale their workloads, this allows them to help manage the thermal envelope of the device. Test: Manual, modify Filament test app to call and observe values Test: PowerManagerTest#testGetThermalHeadroom Test: atest android.os.cts.PowerManager_ThermalTest Test: atest com.android.systemui.statusbar.phone.StatusBarTest Test: atest CachedDeviceStateServiceTest Test: atest AbstractAccessibilityServiceConnectionTest Test: atest KeyEventDispatcherTest Test: atest AttentionManagerServiceTest Test: atest HdmiControlServiceTest Test: atest ThermalManagerServiceTest Test: atest RecoverySystemServiceTest Bug: 136285293 Change-Id: I26e1c849c4b08cbf7ee0057f555cf0893750da43
This commit is contained in:
@@ -36625,6 +36625,7 @@ package android.os {
|
||||
method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener);
|
||||
method public int getCurrentThermalStatus();
|
||||
method public int getLocationPowerSaveMode();
|
||||
method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
|
||||
method public boolean isDeviceIdleMode();
|
||||
method public boolean isIgnoringBatteryOptimizations(String);
|
||||
method public boolean isInteractive();
|
||||
|
||||
@@ -148,6 +148,7 @@ import android.os.IHardwarePropertiesManager;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IRecoverySystem;
|
||||
import android.os.ISystemUpdateManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.IUserManager;
|
||||
import android.os.IncidentManager;
|
||||
import android.os.PowerManager;
|
||||
@@ -576,10 +577,12 @@ public final class SystemServiceRegistry {
|
||||
new CachedServiceFetcher<PowerManager>() {
|
||||
@Override
|
||||
public PowerManager createService(ContextImpl ctx) throws ServiceNotFoundException {
|
||||
IBinder b = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE);
|
||||
IPowerManager service = IPowerManager.Stub.asInterface(b);
|
||||
return new PowerManager(ctx.getOuterContext(),
|
||||
service, ctx.mMainThread.getHandler());
|
||||
IBinder powerBinder = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE);
|
||||
IPowerManager powerService = IPowerManager.Stub.asInterface(powerBinder);
|
||||
IBinder thermalBinder = ServiceManager.getServiceOrThrow(Context.THERMAL_SERVICE);
|
||||
IThermalService thermalService = IThermalService.Stub.asInterface(thermalBinder);
|
||||
return new PowerManager(ctx.getOuterContext(), powerService, thermalService,
|
||||
ctx.mMainThread.getHandler());
|
||||
}});
|
||||
|
||||
registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
|
||||
|
||||
@@ -103,4 +103,11 @@ interface IThermalService {
|
||||
* {@hide}
|
||||
*/
|
||||
List<CoolingDevice> getCurrentCoolingDevicesWithType(in int type);
|
||||
|
||||
/**
|
||||
* @param forecastSeconds how many seconds ahead to forecast the provided headroom
|
||||
* @return forecasted thermal headroom, normalized such that 1.0 indicates that throttling will
|
||||
* occur; returns NaN if the headroom or forecast is unavailable
|
||||
*/
|
||||
float getThermalHeadroom(int forecastSeconds);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package android.os;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.CurrentTimeMillisLong;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
@@ -39,6 +41,7 @@ import com.android.internal.util.Preconditions;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* This class gives you control of the power state of the device.
|
||||
@@ -916,20 +919,22 @@ public final class PowerManager {
|
||||
final IPowerManager mService;
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
|
||||
final Handler mHandler;
|
||||
final IThermalService mThermalService;
|
||||
|
||||
/** We lazily initialize it.*/
|
||||
private DeviceIdleManager mDeviceIdleManager;
|
||||
|
||||
IThermalService mThermalService;
|
||||
private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
|
||||
mListenerMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public PowerManager(Context context, IPowerManager service, Handler handler) {
|
||||
public PowerManager(Context context, IPowerManager service, IThermalService thermalService,
|
||||
Handler handler) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
mThermalService = thermalService;
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
@@ -1877,18 +1882,11 @@ public final class PowerManager {
|
||||
* thermal throttling.
|
||||
*/
|
||||
public @ThermalStatus int getCurrentThermalStatus() {
|
||||
synchronized (this) {
|
||||
if (mThermalService == null) {
|
||||
mThermalService = IThermalService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.THERMAL_SERVICE));
|
||||
}
|
||||
try {
|
||||
return mThermalService.getCurrentThermalStatus();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
try {
|
||||
return mThermalService.getCurrentThermalStatus();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1915,13 +1913,7 @@ public final class PowerManager {
|
||||
*/
|
||||
public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
|
||||
Preconditions.checkNotNull(listener, "listener cannot be null");
|
||||
synchronized (this) {
|
||||
if (mThermalService == null) {
|
||||
mThermalService = IThermalService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.THERMAL_SERVICE));
|
||||
}
|
||||
this.addThermalStatusListener(mContext.getMainExecutor(), listener);
|
||||
}
|
||||
this.addThermalStatusListener(mContext.getMainExecutor(), listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1934,35 +1926,29 @@ public final class PowerManager {
|
||||
@NonNull OnThermalStatusChangedListener listener) {
|
||||
Preconditions.checkNotNull(listener, "listener cannot be null");
|
||||
Preconditions.checkNotNull(executor, "executor cannot be null");
|
||||
synchronized (this) {
|
||||
if (mThermalService == null) {
|
||||
mThermalService = IThermalService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.THERMAL_SERVICE));
|
||||
}
|
||||
Preconditions.checkArgument(!mListenerMap.containsKey(listener),
|
||||
"Listener already registered: " + listener);
|
||||
IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
|
||||
@Override
|
||||
public void onStatusChange(int status) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
listener.onThermalStatusChanged(status);
|
||||
});
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
Preconditions.checkArgument(!mListenerMap.containsKey(listener),
|
||||
"Listener already registered: " + listener);
|
||||
IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
|
||||
@Override
|
||||
public void onStatusChange(int status) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
listener.onThermalStatusChanged(status);
|
||||
});
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (mThermalService.registerThermalStatusListener(internalListener)) {
|
||||
mListenerMap.put(listener, internalListener);
|
||||
} else {
|
||||
throw new RuntimeException("Listener failed to set");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (mThermalService.registerThermalStatusListener(internalListener)) {
|
||||
mListenerMap.put(listener, internalListener);
|
||||
} else {
|
||||
throw new RuntimeException("Listener failed to set");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1973,22 +1959,72 @@ public final class PowerManager {
|
||||
*/
|
||||
public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
|
||||
Preconditions.checkNotNull(listener, "listener cannot be null");
|
||||
synchronized (this) {
|
||||
if (mThermalService == null) {
|
||||
mThermalService = IThermalService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.THERMAL_SERVICE));
|
||||
}
|
||||
IThermalStatusListener internalListener = mListenerMap.get(listener);
|
||||
Preconditions.checkArgument(internalListener != null, "Listener was not added");
|
||||
try {
|
||||
if (mThermalService.unregisterThermalStatusListener(internalListener)) {
|
||||
mListenerMap.remove(listener);
|
||||
} else {
|
||||
throw new RuntimeException("Listener failed to remove");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
IThermalStatusListener internalListener = mListenerMap.get(listener);
|
||||
Preconditions.checkArgument(internalListener != null, "Listener was not added");
|
||||
try {
|
||||
if (mThermalService.unregisterThermalStatusListener(internalListener)) {
|
||||
mListenerMap.remove(listener);
|
||||
} else {
|
||||
throw new RuntimeException("Listener failed to remove");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@CurrentTimeMillisLong
|
||||
private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L);
|
||||
private static final int MINIMUM_HEADROOM_TIME_MILLIS = 500;
|
||||
|
||||
/**
|
||||
* Provides an estimate of how much thermal headroom the device currently has before hitting
|
||||
* severe throttling.
|
||||
*
|
||||
* Note that this only attempts to track the headroom of slow-moving sensors, such as the skin
|
||||
* temperature sensor. This means that there is no benefit to calling this function more
|
||||
* frequently than about once per second, and attempts to call significantly more frequently may
|
||||
* result in the function returning {@code NaN}.
|
||||
*
|
||||
* In addition, in order to be able to provide an accurate forecast, the system does not attempt
|
||||
* to forecast until it has multiple temperature samples from which to extrapolate. This should
|
||||
* only take a few seconds from the time of the first call, but during this time, no forecasting
|
||||
* will occur, and the current headroom will be returned regardless of the value of
|
||||
* {@code forecastSeconds}.
|
||||
*
|
||||
* The value returned is a non-negative float that represents how much of the thermal envelope
|
||||
* is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is (or
|
||||
* will be) throttled at {@link #THERMAL_STATUS_SEVERE}. Such throttling can affect the CPU,
|
||||
* GPU, and other subsystems. Values may exceed 1.0, but there is no implied mapping to specific
|
||||
* thermal status levels beyond that point. This means that values greater than 1.0 may
|
||||
* correspond to {@link #THERMAL_STATUS_SEVERE}, but may also represent heavier throttling.
|
||||
*
|
||||
* A value of 0.0 corresponds to a fixed distance from 1.0, but does not correspond to any
|
||||
* particular thermal status or temperature. Values on (0.0, 1.0] may be expected to scale
|
||||
* linearly with temperature, though temperature changes over time are typically not linear.
|
||||
* Negative values will be clamped to 0.0 before returning.
|
||||
*
|
||||
* @param forecastSeconds how many seconds in the future to forecast. Given that device
|
||||
* conditions may change at any time, forecasts from further in the
|
||||
* future will likely be less accurate than forecasts in the near future.
|
||||
* @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling
|
||||
* threshold, as described above. Returns NaN if the device does not support this
|
||||
* functionality or if this function is called significantly faster than once per
|
||||
* second.
|
||||
*/
|
||||
public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) {
|
||||
// Rate-limit calls into the thermal service
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
long timeSinceLastUpdate = now - mLastHeadroomUpdate.get();
|
||||
if (timeSinceLastUpdate < MINIMUM_HEADROOM_TIME_MILLIS) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
try {
|
||||
float forecast = mThermalService.getThermalHeadroom(forecastSeconds);
|
||||
mLastHeadroomUpdate.set(SystemClock.elapsedRealtime());
|
||||
return forecast;
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,28 @@ public class PowerManagerTest extends AndroidTestCase {
|
||||
.times(1)).onThermalStatusChanged(status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetThermalHeadroom() throws Exception {
|
||||
float headroom = mPm.getThermalHeadroom(0);
|
||||
// If the device doesn't support thermal headroom, return early
|
||||
if (Float.isNaN(headroom)) {
|
||||
return;
|
||||
}
|
||||
assertTrue("Expected non-negative headroom", headroom >= 0.0f);
|
||||
assertTrue("Expected reasonably small headroom", headroom < 10.0f);
|
||||
|
||||
// Call again immediately to ensure rate limiting works
|
||||
headroom = mPm.getThermalHeadroom(0);
|
||||
assertTrue("Expected NaN because of rate limiting", Float.isNaN(headroom));
|
||||
|
||||
// Sleep for a second before attempting to call again so as to not get rate limited
|
||||
Thread.sleep(1000);
|
||||
headroom = mPm.getThermalHeadroom(5);
|
||||
assertFalse("Expected data to still be available", Float.isNaN(headroom));
|
||||
assertTrue("Expected non-negative headroom", headroom >= 0.0f);
|
||||
assertTrue("Expected reasonably small headroom", headroom < 10.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserspaceRebootNotSupported_throwsUnsupportedOperationException() {
|
||||
// Can't use assumption framework with AndroidTestCase :(
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.metrics.LogMaker;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
@@ -256,7 +257,8 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
|
||||
|
||||
IPowerManager powerManagerService = mock(IPowerManager.class);
|
||||
mPowerManager = new PowerManager(mContext, powerManagerService,
|
||||
IThermalService thermalService = mock(IThermalService.class);
|
||||
mPowerManager = new PowerManager(mContext, powerManagerService, thermalService,
|
||||
Handler.createAsync(Looper.myLooper()));
|
||||
|
||||
mNotificationInterruptionStateProvider =
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
|
||||
package com.android.server.power;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.thermal.V1_0.ThermalStatus;
|
||||
import android.hardware.thermal.V1_0.ThermalStatusCode;
|
||||
import android.hardware.thermal.V1_1.IThermalCallback;
|
||||
import android.hardware.thermal.V2_0.IThermalChangedCallback;
|
||||
import android.hardware.thermal.V2_0.TemperatureThreshold;
|
||||
import android.hardware.thermal.V2_0.ThrottlingSeverity;
|
||||
import android.os.Binder;
|
||||
import android.os.CoolingDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.HwBinder;
|
||||
import android.os.IThermalEventListener;
|
||||
import android.os.IThermalService;
|
||||
@@ -36,6 +39,7 @@ import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
import android.os.ShellCommand;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Temperature;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.EventLog;
|
||||
@@ -43,6 +47,7 @@ import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.FgThread;
|
||||
@@ -54,6 +59,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -100,6 +106,9 @@ public class ThermalManagerService extends SystemService {
|
||||
/** Hal ready. */
|
||||
private final AtomicBoolean mHalReady = new AtomicBoolean();
|
||||
|
||||
/** Watches temperatures to forecast when throttling will occur */
|
||||
private final TemperatureWatcher mTemperatureWatcher = new TemperatureWatcher();
|
||||
|
||||
/** Invalid throttling status */
|
||||
private static final int INVALID_THROTTLING = Integer.MIN_VALUE;
|
||||
|
||||
@@ -154,6 +163,7 @@ public class ThermalManagerService extends SystemService {
|
||||
onTemperatureChanged(temperatures.get(i), false);
|
||||
}
|
||||
onTemperatureMapChangedLocked();
|
||||
mTemperatureWatcher.updateSevereThresholds();
|
||||
mHalReady.set(true);
|
||||
}
|
||||
}
|
||||
@@ -462,6 +472,15 @@ public class ThermalManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getThermalHeadroom(int forecastSeconds) {
|
||||
if (!mHalReady.get()) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
return mTemperatureWatcher.getForecast(forecastSeconds);
|
||||
}
|
||||
|
||||
private void dumpItemsLocked(PrintWriter pw, String prefix,
|
||||
Collection<?> items) {
|
||||
for (Iterator iterator = items.iterator(); iterator.hasNext();) {
|
||||
@@ -616,6 +635,10 @@ public class ThermalManagerService extends SystemService {
|
||||
protected abstract List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
|
||||
int type);
|
||||
|
||||
@NonNull
|
||||
protected abstract List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
|
||||
int type);
|
||||
|
||||
protected abstract boolean connectToHal();
|
||||
|
||||
protected abstract void dump(PrintWriter pw, String prefix);
|
||||
@@ -727,6 +750,12 @@ public class ThermalManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
|
||||
int type) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connectToHal() {
|
||||
synchronized (mHalLock) {
|
||||
@@ -856,6 +885,12 @@ public class ThermalManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
|
||||
int type) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connectToHal() {
|
||||
synchronized (mHalLock) {
|
||||
@@ -974,6 +1009,32 @@ public class ThermalManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
|
||||
int type) {
|
||||
synchronized (mHalLock) {
|
||||
List<TemperatureThreshold> ret = new ArrayList<>();
|
||||
if (mThermalHal20 == null) {
|
||||
return ret;
|
||||
}
|
||||
try {
|
||||
mThermalHal20.getTemperatureThresholds(shouldFilter, type,
|
||||
(status, thresholds) -> {
|
||||
if (ThermalStatusCode.SUCCESS == status.code) {
|
||||
ret.addAll(thresholds);
|
||||
} else {
|
||||
Slog.e(TAG,
|
||||
"Couldn't get temperature thresholds because of HAL "
|
||||
+ "error: " + status.debugMessage);
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connectToHal() {
|
||||
synchronized (mHalLock) {
|
||||
@@ -1001,4 +1062,190 @@ public class ThermalManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private class TemperatureWatcher {
|
||||
private final Handler mHandler = BackgroundThread.getHandler();
|
||||
|
||||
/** Map of skin temperature sensor name to a corresponding list of samples */
|
||||
@GuardedBy("mSamples")
|
||||
private final ArrayMap<String, ArrayList<Sample>> mSamples = new ArrayMap<>();
|
||||
|
||||
/** Map of skin temperature sensor name to the corresponding SEVERE temperature threshold */
|
||||
@GuardedBy("mSamples")
|
||||
private ArrayMap<String, Float> mSevereThresholds = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mSamples")
|
||||
private long mLastForecastCallTimeMillis = 0;
|
||||
|
||||
void updateSevereThresholds() {
|
||||
synchronized (mSamples) {
|
||||
List<TemperatureThreshold> thresholds =
|
||||
mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
|
||||
for (int t = 0; t < thresholds.size(); ++t) {
|
||||
TemperatureThreshold threshold = thresholds.get(t);
|
||||
if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) {
|
||||
continue;
|
||||
}
|
||||
float temperature =
|
||||
threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE];
|
||||
if (!Float.isNaN(temperature)) {
|
||||
mSevereThresholds.put(threshold.name,
|
||||
threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
|
||||
private static final int RING_BUFFER_SIZE = 30;
|
||||
|
||||
private void updateTemperature() {
|
||||
synchronized (mSamples) {
|
||||
if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
|
||||
< INACTIVITY_THRESHOLD_MILLIS) {
|
||||
// Trigger this again after a second as long as forecast has been called more
|
||||
// recently than the inactivity timeout
|
||||
mHandler.postDelayed(this::updateTemperature, 1000);
|
||||
} else {
|
||||
// Otherwise, we've been idle for at least 10 seconds, so we should
|
||||
// shut down
|
||||
mSamples.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
|
||||
Temperature.TYPE_SKIN);
|
||||
|
||||
for (int t = 0; t < temperatures.size(); ++t) {
|
||||
Temperature temperature = temperatures.get(t);
|
||||
|
||||
// Filter out invalid temperatures. If this results in no values being stored at
|
||||
// all, the mSamples.empty() check in getForecast() will catch it.
|
||||
if (Float.isNaN(temperature.getValue())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
|
||||
k -> new ArrayList<>(RING_BUFFER_SIZE));
|
||||
if (samples.size() == RING_BUFFER_SIZE) {
|
||||
samples.remove(0);
|
||||
}
|
||||
samples.add(new Sample(now, temperature.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the trend using a linear regression. As the samples are degrees Celsius with
|
||||
* associated timestamps in milliseconds, the slope is in degrees Celsius per millisecond.
|
||||
*/
|
||||
private float getSlopeOf(List<Sample> samples) {
|
||||
long sumTimes = 0L;
|
||||
float sumTemperatures = 0.0f;
|
||||
for (int s = 0; s < samples.size(); ++s) {
|
||||
Sample sample = samples.get(s);
|
||||
sumTimes += sample.time;
|
||||
sumTemperatures += sample.temperature;
|
||||
}
|
||||
long meanTime = sumTimes / samples.size();
|
||||
float meanTemperature = sumTemperatures / samples.size();
|
||||
|
||||
long sampleVariance = 0L;
|
||||
float sampleCovariance = 0.0f;
|
||||
for (int s = 0; s < samples.size(); ++s) {
|
||||
Sample sample = samples.get(s);
|
||||
long timeDelta = sample.time - meanTime;
|
||||
float temperatureDelta = sample.temperature - meanTemperature;
|
||||
sampleVariance += timeDelta * timeDelta;
|
||||
sampleCovariance += timeDelta * temperatureDelta;
|
||||
}
|
||||
|
||||
return sampleCovariance / sampleVariance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine the temperature corresponding to 0.0. Given that 1.0 is pinned at the
|
||||
* temperature corresponding to the SEVERE threshold, we set 0.0 to be that temperature
|
||||
* minus DEGREES_BETWEEN_ZERO_AND_ONE.
|
||||
*/
|
||||
private static final float DEGREES_BETWEEN_ZERO_AND_ONE = 30.0f;
|
||||
|
||||
private float normalizeTemperature(float temperature, float severeThreshold) {
|
||||
synchronized (mSamples) {
|
||||
float zeroNormalized = severeThreshold - DEGREES_BETWEEN_ZERO_AND_ONE;
|
||||
if (temperature <= zeroNormalized) {
|
||||
return 0.0f;
|
||||
}
|
||||
float delta = temperature - zeroNormalized;
|
||||
return delta / DEGREES_BETWEEN_ZERO_AND_ONE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MINIMUM_SAMPLE_COUNT = 3;
|
||||
|
||||
float getForecast(int forecastSeconds) {
|
||||
synchronized (mSamples) {
|
||||
mLastForecastCallTimeMillis = System.currentTimeMillis();
|
||||
if (mSamples.isEmpty()) {
|
||||
updateTemperature();
|
||||
}
|
||||
|
||||
// If somehow things take much longer than expected or there are no temperatures
|
||||
// to sample, return early
|
||||
if (mSamples.isEmpty()) {
|
||||
Slog.e(TAG, "No temperature samples found");
|
||||
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");
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
float maxNormalized = Float.NaN;
|
||||
for (Map.Entry<String, ArrayList<Sample>> entry : mSamples.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
ArrayList<Sample> samples = entry.getValue();
|
||||
|
||||
Float threshold = mSevereThresholds.get(name);
|
||||
if (threshold == null) {
|
||||
Slog.e(TAG, "No threshold found for " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
float currentTemperature = samples.get(0).temperature;
|
||||
|
||||
if (samples.size() < MINIMUM_SAMPLE_COUNT) {
|
||||
// Don't try to forecast, just use the latest one we have
|
||||
float normalized = normalizeTemperature(currentTemperature, threshold);
|
||||
if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
|
||||
maxNormalized = normalized;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
float slope = getSlopeOf(samples);
|
||||
float normalized = normalizeTemperature(
|
||||
currentTemperature + slope * forecastSeconds * 1000, threshold);
|
||||
if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
|
||||
maxNormalized = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return maxNormalized;
|
||||
}
|
||||
}
|
||||
|
||||
private class Sample {
|
||||
public long time;
|
||||
public float temperature;
|
||||
|
||||
Sample(long time, float temperature) {
|
||||
this.time = time;
|
||||
this.temperature = temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.Intent;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.BatteryManagerInternal;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.OsProtoEnums;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
@@ -52,13 +53,14 @@ import org.mockito.MockitoAnnotations;
|
||||
public class CachedDeviceStateServiceTest {
|
||||
@Mock private BatteryManagerInternal mBatteryManager;
|
||||
@Mock private IPowerManager mPowerManager;
|
||||
@Mock private IThermalService mThermalService;
|
||||
private BroadcastInterceptingContext mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PowerManager powerManager = new PowerManager(context, mPowerManager, null);
|
||||
PowerManager powerManager = new PowerManager(context, mPowerManager, mThermalService, null);
|
||||
mContext = new BroadcastInterceptingContext(context) {
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
|
||||
@@ -74,6 +74,7 @@ import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
@@ -153,6 +154,7 @@ public class AbstractAccessibilityServiceConnectionTest {
|
||||
|
||||
@Mock private Context mMockContext;
|
||||
@Mock private IPowerManager mMockIPowerManager;
|
||||
@Mock private IThermalService mMockIThermalService;
|
||||
@Mock private PackageManager mMockPackageManager;
|
||||
@Spy private AccessibilityServiceInfo mSpyServiceInfo = new AccessibilityServiceInfo();
|
||||
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
|
||||
@@ -180,7 +182,7 @@ public class AbstractAccessibilityServiceConnectionTest {
|
||||
.thenReturn(mMockMagnificationController);
|
||||
|
||||
PowerManager powerManager =
|
||||
new PowerManager(mMockContext, mMockIPowerManager, mHandler);
|
||||
new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
|
||||
when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
|
||||
when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
|
||||
|
||||
@@ -34,6 +34,7 @@ import static org.mockito.hamcrest.MockitoHamcrest.argThat;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
@@ -73,6 +74,7 @@ public class KeyEventDispatcherTest {
|
||||
private KeyEventFilter mKeyEventFilter1;
|
||||
private KeyEventFilter mKeyEventFilter2;
|
||||
private IPowerManager mMockPowerManagerService;
|
||||
private IThermalService mMockThermalService;
|
||||
private MessageCapturingHandler mMessageCapturingHandler;
|
||||
private ArgumentCaptor<Integer> mFilter1SequenceCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
private ArgumentCaptor<Integer> mFilter2SequenceCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
@@ -82,10 +84,12 @@ public class KeyEventDispatcherTest {
|
||||
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
|
||||
mInputEventsHandler = new MessageCapturingHandler(looper, null);
|
||||
mMockPowerManagerService = mock(IPowerManager.class);
|
||||
mMockThermalService = mock(IThermalService.class);
|
||||
// TODO: It would be better to mock PowerManager rather than its binder, but the class is
|
||||
// final.
|
||||
PowerManager powerManager =
|
||||
new PowerManager(mock(Context.class), mMockPowerManagerService, new Handler(looper));
|
||||
new PowerManager(mock(Context.class), mMockPowerManagerService, mMockThermalService,
|
||||
new Handler(looper));
|
||||
mMessageCapturingHandler = new MessageCapturingHandler(looper, null);
|
||||
mKeyEventDispatcher = new KeyEventDispatcher(mInputEventsHandler, SEND_FRAMEWORK_KEY_EVENT,
|
||||
mLock, powerManager, mMessageCapturingHandler);
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.DeviceConfig;
|
||||
@@ -74,6 +75,8 @@ public class AttentionManagerServiceTest {
|
||||
@Mock
|
||||
private IPowerManager mMockIPowerManager;
|
||||
@Mock
|
||||
private IThermalService mMockIThermalService;
|
||||
@Mock
|
||||
Context mContext;
|
||||
|
||||
@Before
|
||||
@@ -84,7 +87,7 @@ public class AttentionManagerServiceTest {
|
||||
// setup power manager mock
|
||||
PowerManager mPowerManager;
|
||||
doReturn(true).when(mMockIPowerManager).isInteractive();
|
||||
mPowerManager = new PowerManager(mContext, mMockIPowerManager, null);
|
||||
mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null);
|
||||
|
||||
Object mLock = new Object();
|
||||
// setup a spy on attention manager
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.content.ContextWrapper;
|
||||
import android.hardware.hdmi.HdmiControlManager;
|
||||
import android.hardware.hdmi.HdmiPortInfo;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IThermalService;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
@@ -123,6 +124,7 @@ public class HdmiControlServiceTest {
|
||||
private HdmiPortInfo[] mHdmiPortInfo;
|
||||
|
||||
@Mock private IPowerManager mIPowerManagerMock;
|
||||
@Mock private IThermalService mIThermalServiceMock;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -130,7 +132,8 @@ public class HdmiControlServiceTest {
|
||||
|
||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||
|
||||
PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null);
|
||||
PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
|
||||
mIThermalServiceMock, null);
|
||||
when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
|
||||
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
|
||||
|
||||
|
||||
@@ -28,10 +28,12 @@ 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.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;
|
||||
@@ -72,6 +74,8 @@ public class ThermalManagerServiceTest {
|
||||
@Mock
|
||||
private IPowerManager mIPowerManagerMock;
|
||||
@Mock
|
||||
private IThermalService mIThermalServiceMock;
|
||||
@Mock
|
||||
private IThermalEventListener mEventListener1;
|
||||
@Mock
|
||||
private IThermalEventListener mEventListener2;
|
||||
@@ -132,6 +136,12 @@ public class ThermalManagerServiceTest {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
|
||||
int type) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connectToHal() {
|
||||
return true;
|
||||
@@ -153,7 +163,7 @@ public class ThermalManagerServiceTest {
|
||||
public void setUp() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mFakeHal = new ThermalHalFake();
|
||||
mPowerManager = new PowerManager(mContext, mIPowerManagerMock, null);
|
||||
mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock, null);
|
||||
when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
|
||||
when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
|
||||
resetListenerMock();
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.content.IntentSender;
|
||||
import android.os.Handler;
|
||||
import android.os.IPowerManager;
|
||||
import android.os.IRecoverySystemProgressListener;
|
||||
import android.os.IThermalService;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
|
||||
@@ -62,6 +63,7 @@ public class RecoverySystemServiceTest {
|
||||
private RecoverySystemService.UncryptSocket mUncryptSocket;
|
||||
private Context mContext;
|
||||
private IPowerManager mIPowerManager;
|
||||
private IThermalService mIThermalService;
|
||||
private FileWriter mUncryptUpdateFileWriter;
|
||||
private LockSettingsInternal mLockSettingsInternal;
|
||||
|
||||
@@ -77,8 +79,9 @@ public class RecoverySystemServiceTest {
|
||||
|
||||
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
|
||||
mIPowerManager = mock(IPowerManager.class);
|
||||
mIThermalService = mock(IThermalService.class);
|
||||
PowerManager powerManager = new PowerManager(mock(Context.class), mIPowerManager,
|
||||
new Handler(looper));
|
||||
mIThermalService, new Handler(looper));
|
||||
|
||||
mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
|
||||
powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal);
|
||||
|
||||
Reference in New Issue
Block a user