Merge "Add PowerManager#getThermalHeadroom"
This commit is contained in:
@@ -36633,6 +36633,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;
|
||||
@@ -147,6 +148,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;
|
||||
@@ -174,7 +176,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