Add thermal status API for app and unit test
Test: Manually on Thermal HAL 2.0 device
Test: Manually on Thermal HAL 1.1 device
Test: Manually on no Thermal HAL emulator
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
Bug: 111086696
Bug: 119413961
Change-Id: I6723406123d12339e82e9e87eec14b7f9a301897
diff --git a/Android.bp b/Android.bp
index 3095ca5..8f649e5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -254,6 +254,7 @@
":statsd_aidl",
"core/java/android/os/ISystemUpdateManager.aidl",
"core/java/android/os/IThermalEventListener.aidl",
+ "core/java/android/os/IThermalStatusListener.aidl",
"core/java/android/os/IThermalService.aidl",
"core/java/android/os/IUpdateLock.aidl",
"core/java/android/os/IUserManager.aidl",
diff --git a/core/java/android/os/IThermalService.aidl b/core/java/android/os/IThermalService.aidl
index 287a5ed..8160338 100644
--- a/core/java/android/os/IThermalService.aidl
+++ b/core/java/android/os/IThermalService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IThermalEventListener;
+import android.os.IThermalStatusListener;
import android.os.Temperature;
import java.util.List;
@@ -30,31 +31,60 @@
* @param listener the IThermalEventListener to be notified.
* {@hide}
*/
- void registerThermalEventListener(in IThermalEventListener listener);
+ boolean registerThermalEventListener(in IThermalEventListener listener);
+
/**
* Register a listener for thermal events on given temperature type.
* @param listener the IThermalEventListener to be notified.
* @param type the temperature type IThermalEventListener to be notified.
+ * @return true if registered successfully.
* {@hide}
*/
- void registerThermalEventListenerWithType(in IThermalEventListener listener, in int type);
+ boolean registerThermalEventListenerWithType(in IThermalEventListener listener, in int type);
+
/**
* Unregister a previously-registered listener for thermal events.
* @param listener the IThermalEventListener to no longer be notified.
+ * @return true if unregistered successfully.
* {@hide}
*/
- void unregisterThermalEventListener(in IThermalEventListener listener);
+ boolean unregisterThermalEventListener(in IThermalEventListener listener);
+
/**
* Get current temperature with its throttling status.
* @return list of android.os.Temperature
* {@hide}
*/
List<Temperature> getCurrentTemperatures();
+
/**
* Get current temperature with its throttling status on given temperature type.
* @param type the temperature type to query.
- * @return list of android.os.Temperature
+ * @return list of {@link android.os.Temperature}.
* {@hide}
*/
List<Temperature> getCurrentTemperaturesWithType(in int type);
+
+ /**
+ * Register a listener for thermal status change.
+ * @param listener the IThermalStatusListener to be notified.
+ * @return true if registered successfully.
+ * {@hide}
+ */
+ boolean registerThermalStatusListener(in IThermalStatusListener listener);
+
+ /**
+ * Unregister a previously-registered listener for thermal status.
+ * @param listener the IThermalStatusListener to no longer be notified.
+ * @return true if unregistered successfully.
+ * {@hide}
+ */
+ boolean unregisterThermalStatusListener(in IThermalStatusListener listener);
+
+ /**
+ * Get current thermal status.
+ * @return status defined in {@link android.os.Temperature}.
+ * {@hide}
+ */
+ int getCurrentStatus();
}
diff --git a/core/java/android/os/IThermalStatusListener.aidl b/core/java/android/os/IThermalStatusListener.aidl
new file mode 100644
index 0000000..a6da7d0
--- /dev/null
+++ b/core/java/android/os/IThermalStatusListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+/**
+ * Listener for thermal status.
+ * {@hide}
+ */
+oneway interface IThermalStatusListener {
+ /**
+ * Called when overall thermal throttling status changed.
+ * @param status defined in {@link android.os.Temperature#ThrottlingStatus}.
+ */
+ void onStatusChange(int status);
+}
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index 37ed52c..bf85fbd 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -25,9 +25,7 @@
/**
* Temperature values used by IThermalService.
- */
-
-/**
+ *
* @hide
*/
public class Temperature implements Parcelable {
@@ -40,7 +38,6 @@
/** The level of the sensor is currently in throttling */
private int mStatus;
- /** @hide */
@IntDef(prefix = { "THROTTLING_" }, value = {
THROTTLING_NONE,
THROTTLING_LIGHT,
@@ -62,7 +59,6 @@
public static final int THROTTLING_WARNING = ThrottlingSeverity.WARNING;
public static final int THROTTLING_SHUTDOWN = ThrottlingSeverity.SHUTDOWN;
- /** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_UNKNOWN,
TYPE_CPU,
@@ -95,19 +91,28 @@
*
* @return true if a temperature type is valid otherwise false.
*/
- public static boolean isValidType(int type) {
+ public static boolean isValidType(@Type int type) {
return type >= TYPE_UNKNOWN && type <= TYPE_BCL_PERCENTAGE;
}
+ /**
+ * Verify a valid throttling status.
+ *
+ * @return true if a status is valid otherwise false.
+ */
+ public static boolean isValidStatus(@ThrottlingStatus int status) {
+ return status >= THROTTLING_NONE && status <= THROTTLING_SHUTDOWN;
+ }
+
public Temperature() {
this(Float.NaN, TYPE_UNKNOWN, "", THROTTLING_NONE);
}
- public Temperature(float value, @Type int type, String name, int status) {
+ public Temperature(float value, @Type int type, String name, @ThrottlingStatus int status) {
mValue = value;
mType = isValidType(type) ? type : TYPE_UNKNOWN;
mName = name;
- mStatus = status;
+ mStatus = isValidStatus(status) ? status : THROTTLING_NONE;
}
/**
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 812fd82..79e2688 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.power;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.thermal.V1_0.ThermalStatus;
import android.hardware.thermal.V1_0.ThermalStatusCode;
@@ -26,12 +27,16 @@
import android.os.HwBinder;
import android.os.IThermalEventListener;
import android.os.IThermalService;
+import android.os.IThermalStatusListener;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.Temperature;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
@@ -39,6 +44,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
@@ -51,210 +57,154 @@
public class ThermalManagerService extends SystemService {
private static final String TAG = ThermalManagerService.class.getSimpleName();
- /** Registered observers of the thermal changed events. Cookie is used to store type */
+ /** Lock to protect listen list. */
+ private final Object mLock = new Object();
+
+ /**
+ * Registered observers of the thermal events. Cookie is used to store type as Integer, null
+ * means no filter.
+ */
@GuardedBy("mLock")
private final RemoteCallbackList<IThermalEventListener> mThermalEventListeners =
new RemoteCallbackList<>();
- /** Lock to protect HAL handles and listen list. */
- private final Object mLock = new Object();
-
- /** Newly registered callback. */
+ /** Registered observers of the thermal status. */
@GuardedBy("mLock")
- private IThermalEventListener mNewListenerCallback = null;
+ private final RemoteCallbackList<IThermalStatusListener> mThermalStatusListeners =
+ new RemoteCallbackList<>();
- /** Newly registered callback type, null means not filter type. */
+ /** Current thermal status */
@GuardedBy("mLock")
- private Integer mNewListenerType = null;
+ private int mStatus;
+
+ /** Current thermal map, key as name */
+ @GuardedBy("mLock")
+ private ArrayMap<String, Temperature> mTemperatureMap = new ArrayMap<>();
/** Local PMS handle. */
private final PowerManager mPowerManager;
- /** Proxy object for the Thermal HAL 2.0 service. */
+ /** HAL wrapper. */
+ private ThermalHalWrapper mHalWrapper;
+
+ /** Hal ready. */
@GuardedBy("mLock")
- private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
+ private boolean mHalReady;
- /** Proxy object for the Thermal HAL 1.1 service. */
- @GuardedBy("mLock")
- private android.hardware.thermal.V1_1.IThermal mThermalHal11 = null;
-
- /** Cookie for matching the right end point. */
- private static final int THERMAL_HAL_DEATH_COOKIE = 5612;
-
- /** HWbinder callback for Thermal HAL 2.0. */
- private final IThermalChangedCallback.Stub mThermalCallback20 =
- new IThermalChangedCallback.Stub() {
- @Override
- public void notifyThrottling(
- android.hardware.thermal.V2_0.Temperature temperature) {
- android.os.Temperature thermalSvcTemp = new android.os.Temperature(
- temperature.value, temperature.type, temperature.name,
- temperature.throttlingStatus);
- final long token = Binder.clearCallingIdentity();
- try {
- notifyThrottlingImpl(thermalSvcTemp);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- };
-
- /** HWbinder callback for Thermal HAL 1.1. */
- private final IThermalCallback.Stub mThermalCallback11 =
- new IThermalCallback.Stub() {
- @Override
- public void notifyThrottling(boolean isThrottling,
- android.hardware.thermal.V1_0.Temperature temperature) {
- android.os.Temperature thermalSvcTemp = new android.os.Temperature(
- temperature.currentValue, temperature.type, temperature.name,
- isThrottling ? ThrottlingSeverity.SEVERE : ThrottlingSeverity.NONE);
- final long token = Binder.clearCallingIdentity();
- try {
- notifyThrottlingImpl(thermalSvcTemp);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- };
+ /** Invalid throttling status */
+ private static final int INVALID_THROTTLING = Integer.MIN_VALUE;
public ThermalManagerService(Context context) {
+ this(context, null);
+ }
+
+ @VisibleForTesting
+ ThermalManagerService(Context context, @Nullable ThermalHalWrapper halWrapper) {
super(context);
mPowerManager = context.getSystemService(PowerManager.class);
+ mHalWrapper = halWrapper;
+ // Initialize to invalid to send status onActivityManagerReady
+ mStatus = INVALID_THROTTLING;
}
- private void setNewListener(IThermalEventListener listener, Integer type) {
- synchronized (mLock) {
- mNewListenerCallback = listener;
- mNewListenerType = type;
+ @Override
+ public void onStart() {
+ publishBinderService(Context.THERMAL_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ onActivityManagerReady();
}
}
- private void clearNewListener() {
+ private void onActivityManagerReady() {
synchronized (mLock) {
- mNewListenerCallback = null;
- mNewListenerType = null;
- }
- }
-
- private final IThermalService.Stub mService = new IThermalService.Stub() {
- @Override
- public void registerThermalEventListener(IThermalEventListener listener) {
- synchronized (mLock) {
- mThermalEventListeners.register(listener, null);
- // Notify its callback after new client registered.
- setNewListener(listener, null);
- long token = Binder.clearCallingIdentity();
- try {
- notifyCurrentTemperaturesLocked();
- } finally {
- Binder.restoreCallingIdentity(token);
- clearNewListener();
+ // Connect to HAL and post to listeners.
+ boolean halConnected = (mHalWrapper != null);
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal20Wrapper();
+ halConnected = mHalWrapper.connectToHal();
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal11Wrapper();
+ halConnected = mHalWrapper.connectToHal();
}
}
- }
-
- @Override
- public void registerThermalEventListenerWithType(IThermalEventListener listener, int type) {
- synchronized (mLock) {
- mThermalEventListeners.register(listener, new Integer(type));
- setNewListener(listener, new Integer(type));
- // Notify its callback after new client registered.
- long token = Binder.clearCallingIdentity();
- try {
- notifyCurrentTemperaturesLocked();
- } finally {
- Binder.restoreCallingIdentity(token);
- clearNewListener();
- }
+ mHalWrapper.setCallback(this::onTemperatureChangedCallback);
+ if (!halConnected) {
+ return;
}
- }
-
- @Override
- public void unregisterThermalEventListener(IThermalEventListener listener) {
- synchronized (mLock) {
- long token = Binder.clearCallingIdentity();
- try {
- mThermalEventListeners.unregister(listener);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(false,
+ 0);
+ final int count = temperatures.size();
+ for (int i = 0; i < count; i++) {
+ onTemperatureChanged(temperatures.get(i), false);
}
+ onTemperatureMapChangedLocked();
+ mHalReady = halConnected /* true */;
}
-
- @Override
- public List<android.os.Temperature> getCurrentTemperatures() {
- List<android.os.Temperature> ret;
- long token = Binder.clearCallingIdentity();
- try {
- ret = getCurrentTemperaturesInternal(false, 0 /* not used */);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return ret;
- }
-
- @Override
- public List<android.os.Temperature> getCurrentTemperaturesWithType(int type) {
- List<android.os.Temperature> ret;
- long token = Binder.clearCallingIdentity();
- try {
- ret = getCurrentTemperaturesInternal(true, type);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return ret;
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
- pw.println("ThermalEventListeners dump:");
- synchronized (mLock) {
- mThermalEventListeners.dump(pw, "\t");
- pw.println("ThermalHAL 1.1 connected: " + (mThermalHal11 != null ? "yes" : "no"));
- pw.println("ThermalHAL 2.0 connected: " + (mThermalHal20 != null ? "yes" : "no"));
- }
- }
- };
-
- private List<android.os.Temperature> getCurrentTemperaturesInternal(boolean shouldFilter,
- int type) {
- List<android.os.Temperature> ret = new ArrayList<>();
- synchronized (mLock) {
- if (mThermalHal20 == null) {
- return ret;
- }
- try {
- mThermalHal20.getCurrentTemperatures(shouldFilter, type,
- (ThermalStatus status,
- ArrayList<android.hardware.thermal.V2_0.Temperature>
- temperatures) -> {
- if (ThermalStatusCode.SUCCESS == status.code) {
- for (android.hardware.thermal.V2_0.Temperature
- temperature : temperatures) {
- ret.add(new android.os.Temperature(
- temperature.value, temperature.type, temperature.name,
- temperature.throttlingStatus));
- }
- } else {
- Slog.e(TAG,
- "Couldn't get temperatures because of HAL error: "
- + status.debugMessage);
- }
-
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
- connectToHalLocked();
- // Post to listeners after reconnect to HAL.
- notifyCurrentTemperaturesLocked();
- }
- }
- return ret;
}
- private void notifyListener(android.os.Temperature temperature, IThermalEventListener listener,
- Integer type) {
+ private void postStatusListener(IThermalStatusListener listener) {
+ final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
+ try {
+ listener.onStatusChange(mStatus);
+ } catch (RemoteException | RuntimeException e) {
+ Slog.e(TAG, "Thermal callback failed to call", e);
+ }
+ });
+ if (!thermalCallbackQueued) {
+ Slog.e(TAG, "Thermal callback failed to queue");
+ }
+ }
+
+ private void notifyStatusListenersLocked() {
+ if (!Temperature.isValidStatus(mStatus)) {
+ return;
+ }
+ final int length = mThermalStatusListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final IThermalStatusListener listener =
+ mThermalStatusListeners.getBroadcastItem(i);
+ postStatusListener(listener);
+ }
+ } finally {
+ mThermalStatusListeners.finishBroadcast();
+ }
+ }
+
+ private void onTemperatureMapChangedLocked() {
+ int newStatus = INVALID_THROTTLING;
+ final int count = mTemperatureMap.size();
+ for (int i = 0; i < count; i++) {
+ Temperature t = mTemperatureMap.valueAt(i);
+ if (t.getStatus() >= newStatus) {
+ newStatus = t.getStatus();
+ }
+ }
+ if (newStatus != mStatus) {
+ mStatus = newStatus;
+ notifyStatusListenersLocked();
+ }
+ }
+
+
+ private void postEventListenerCurrentTemperatures(IThermalEventListener listener,
+ @Nullable Integer type) {
+ synchronized (mLock) {
+ final int count = mTemperatureMap.size();
+ for (int i = 0; i < count; i++) {
+ postEventListener(mTemperatureMap.valueAt(i), listener,
+ type);
+ }
+ }
+ }
+
+ private void postEventListener(Temperature temperature,
+ IThermalEventListener listener,
+ @Nullable Integer type) {
// Skip if listener registered with a different type
if (type != null && type != temperature.getType()) {
return;
@@ -271,11 +221,26 @@
}
}
- private void notifyThrottlingImpl(android.os.Temperature temperature) {
+ private void notifyEventListenersLocked(Temperature temperature) {
+ final int length = mThermalEventListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final IThermalEventListener listener =
+ mThermalEventListeners.getBroadcastItem(i);
+ final Integer type =
+ (Integer) mThermalEventListeners.getBroadcastCookie(i);
+ postEventListener(temperature, listener, type);
+ }
+ } finally {
+ mThermalEventListeners.finishBroadcast();
+ }
+ }
+
+ private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
synchronized (mLock) {
// Thermal Shutdown for Skin temperature
- if (temperature.getStatus() == android.os.Temperature.THROTTLING_SHUTDOWN
- && temperature.getType() == android.os.Temperature.TYPE_SKIN) {
+ if (temperature.getStatus() == Temperature.THROTTLING_SHUTDOWN
+ && temperature.getType() == Temperature.TYPE_SKIN) {
final long token = Binder.clearCallingIdentity();
try {
mPowerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
@@ -284,107 +249,420 @@
}
}
- if (mNewListenerCallback != null) {
- // Only notify current newly added callback.
- notifyListener(temperature, mNewListenerCallback, mNewListenerType);
+ Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
+ if (old != null) {
+ if (old.getStatus() != temperature.getStatus()) {
+ notifyEventListenersLocked(temperature);
+ }
} else {
- final int length = mThermalEventListeners.beginBroadcast();
- try {
- for (int i = 0; i < length; i++) {
- final IThermalEventListener listener =
- mThermalEventListeners.getBroadcastItem(i);
- final Integer type = (Integer) mThermalEventListeners.getBroadcastCookie(i);
- notifyListener(temperature, listener, type);
- }
- } finally {
- mThermalEventListeners.finishBroadcast();
- }
+ notifyEventListenersLocked(temperature);
+ }
+ if (sendStatus) {
+ onTemperatureMapChangedLocked();
}
}
}
- @Override
- public void onStart() {
- publishBinderService(Context.THERMAL_SERVICE, mService);
+ private void onTemperatureChangedCallback(Temperature temperature) {
+ onTemperatureChanged(temperature, true);
}
- @Override
- public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
- onActivityManagerReady();
+ private void dumpTemperaturesLocked(PrintWriter pw, String prefix,
+ Collection<Temperature> temperatures) {
+ for (Temperature t : temperatures) {
+ pw.print(prefix);
+ String out = String.format("Name: %s, Type: %d, Status: %d, Value: %f",
+ t.getName(),
+ t.getType(),
+ t.getStatus(),
+ t.getValue()
+ );
+ pw.println(out);
}
}
- private void notifyCurrentTemperaturesCallbackLocked(ThermalStatus status,
- ArrayList<android.hardware.thermal.V2_0.Temperature> temperatures) {
- if (ThermalStatusCode.SUCCESS != status.code) {
- Slog.e(TAG, "Couldn't get temperatures because of HAL error: "
- + status.debugMessage);
- return;
- }
- for (android.hardware.thermal.V2_0.Temperature temperature : temperatures) {
- android.os.Temperature thermal_svc_temp =
- new android.os.Temperature(
- temperature.value, temperature.type,
- temperature.name,
- temperature.throttlingStatus);
- notifyThrottlingImpl(thermal_svc_temp);
- }
- }
-
- private void notifyCurrentTemperaturesLocked() {
- if (mThermalHal20 == null) {
- return;
- }
- try {
- mThermalHal20.getCurrentTemperatures(false, 0,
- this::notifyCurrentTemperaturesCallbackLocked);
- } catch (RemoteException e) {
- Slog.e(TAG, "Couldn't get temperatures, reconnecting...", e);
- connectToHalLocked();
- }
- }
-
- private void onActivityManagerReady() {
- synchronized (mLock) {
- connectToHalLocked();
- // Post to listeners after connect to HAL.
- notifyCurrentTemperaturesLocked();
- }
- }
-
- final class DeathRecipient implements HwBinder.DeathRecipient {
+ @VisibleForTesting
+ final IThermalService.Stub mService = new IThermalService.Stub() {
@Override
- public void serviceDied(long cookie) {
- if (cookie == THERMAL_HAL_DEATH_COOKIE) {
- Slog.e(TAG, "Thermal HAL service died cookie: " + cookie);
+ public boolean registerThermalEventListener(IThermalEventListener listener) {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mThermalEventListeners.register(listener, null)) {
+ return false;
+ }
+ if (mHalReady) {
+ // Notify its callback after new client registered.
+ postEventListenerCurrentTemperatures(listener, null);
+ }
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public boolean registerThermalEventListenerWithType(IThermalEventListener listener,
+ int type) {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mThermalEventListeners.register(listener, new Integer(type))) {
+ return false;
+ }
+ if (mHalReady) {
+ // Notify its callback after new client registered.
+ postEventListenerCurrentTemperatures(listener, new Integer(type));
+ }
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public boolean unregisterThermalEventListener(IThermalEventListener listener) {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mThermalEventListeners.unregister(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public List<Temperature> getCurrentTemperatures() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mHalReady) {
+ return new ArrayList<>();
+ }
+ return mHalWrapper.getCurrentTemperatures(false, 0 /* not used */);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public List<Temperature> getCurrentTemperaturesWithType(int type) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mHalReady) {
+ return new ArrayList<>();
+ }
+ return mHalWrapper.getCurrentTemperatures(true, type);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean registerThermalStatusListener(IThermalStatusListener listener) {
+ synchronized (mLock) {
+ // Notify its callback after new client registered.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mThermalStatusListeners.register(listener)) {
+ return false;
+ }
+ if (mHalReady) {
+ // Notify its callback after new client registered.
+ postStatusListener(listener);
+ }
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public boolean unregisterThermalStatusListener(IThermalStatusListener listener) {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mThermalStatusListeners.unregister(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public int getCurrentStatus() {
+ synchronized (mLock) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return Temperature.isValidStatus(mStatus) ? mStatus
+ : Temperature.THROTTLING_NONE;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
synchronized (mLock) {
- connectToHalLocked();
- // Post to listeners after reconnect to HAL.
- notifyCurrentTemperaturesLocked();
+ pw.println("ThermalEventListeners:");
+ mThermalEventListeners.dump(pw, "\t");
+ pw.println("ThermalStatusListeners:");
+ mThermalStatusListeners.dump(pw, "\t");
+ pw.println("Thermal Status: " + Integer.toString(mStatus));
+ pw.println("Cached temperatures:");
+ dumpTemperaturesLocked(pw, "\t", mTemperatureMap.values());
+ pw.println("HAL Ready: " + Boolean.toString(mHalReady));
+ if (mHalReady) {
+ pw.println("HAL connection:");
+ mHalWrapper.dump(pw, "\t");
+ pw.println("Current temperatures from HAL:");
+ dumpTemperaturesLocked(pw, "\t",
+ mHalWrapper.getCurrentTemperatures(false, 0));
+ }
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ abstract static class ThermalHalWrapper {
+ protected static final String TAG = ThermalHalWrapper.class.getSimpleName();
+
+ /** Lock to protect HAL handle. */
+ protected final Object mHalLock = new Object();
+
+ @FunctionalInterface
+ interface TemperatureChangedCallback {
+ void onValues(Temperature temperature);
+ }
+
+ /** Temperature callback. */
+ protected TemperatureChangedCallback mCallback;
+
+ /** Cookie for matching the right end point. */
+ protected static final int THERMAL_HAL_DEATH_COOKIE = 5612;
+
+ @VisibleForTesting
+ protected void setCallback(TemperatureChangedCallback cb) {
+ mCallback = cb;
+ }
+
+ protected abstract List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+ int type);
+
+ protected abstract boolean connectToHal();
+
+ protected abstract void dump(PrintWriter pw, String prefix);
+
+ protected void resendCurrentTemperatures() {
+ synchronized (mHalLock) {
+ List<Temperature> temperatures = getCurrentTemperatures(false, 0);
+ final int count = temperatures.size();
+ for (int i = 0; i < count; i++) {
+ mCallback.onValues(temperatures.get(i));
+ }
+ }
+ }
+
+ final class DeathRecipient implements HwBinder.DeathRecipient {
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == THERMAL_HAL_DEATH_COOKIE) {
+ Slog.e(TAG, "Thermal HAL service died cookie: " + cookie);
+ synchronized (mHalLock) {
+ connectToHal();
+ // Post to listeners after reconnect to HAL.
+ resendCurrentTemperatures();
+ }
}
}
}
}
- private void connectToHalLocked() {
- try {
- mThermalHal20 = android.hardware.thermal.V2_0.IThermal.getService();
- mThermalHal20.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
- mThermalHal20.registerThermalChangedCallback(mThermalCallback20, false,
- 0 /* not used */);
- } catch (NoSuchElementException | RemoteException e) {
- Slog.e(TAG, "Thermal HAL 2.0 service not connected, trying 1.1.");
- mThermalHal20 = null;
- try {
- mThermalHal11 = android.hardware.thermal.V1_1.IThermal.getService();
- mThermalHal11.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
- mThermalHal11.registerThermalCallback(mThermalCallback11);
- } catch (NoSuchElementException | RemoteException e2) {
- Slog.e(TAG,
- "Thermal HAL 1.1 service not connected, no thermal call back "
- + "will be called.");
- mThermalHal11 = null;
+ static class ThermalHal11Wrapper extends ThermalHalWrapper {
+ /** Proxy object for the Thermal HAL 1.1 service. */
+ @GuardedBy("mHalLock")
+ private android.hardware.thermal.V1_1.IThermal mThermalHal11 = null;
+
+ /** HWbinder callback for Thermal HAL 1.1. */
+ private final IThermalCallback.Stub mThermalCallback11 =
+ new IThermalCallback.Stub() {
+ @Override
+ public void notifyThrottling(boolean isThrottling,
+ android.hardware.thermal.V1_0.Temperature temperature) {
+ Temperature thermalSvcTemp = new Temperature(
+ temperature.currentValue, temperature.type, temperature.name,
+ isThrottling ? ThrottlingSeverity.SEVERE
+ : ThrottlingSeverity.NONE);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallback.onValues(thermalSvcTemp);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ @Override
+ protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+ int type) {
+ synchronized (mHalLock) {
+ List<Temperature> ret = new ArrayList<>();
+ if (mThermalHal11 == null) {
+ return ret;
+ }
+ try {
+ mThermalHal11.getTemperatures(
+ (ThermalStatus status,
+ ArrayList<android.hardware.thermal.V1_0.Temperature>
+ temperatures) -> {
+ if (ThermalStatusCode.SUCCESS == status.code) {
+ for (android.hardware.thermal.V1_0.Temperature
+ temperature : temperatures) {
+ if (shouldFilter && type != temperature.type) {
+ continue;
+ }
+ // Thermal HAL 1.1 doesn't report current throttling status
+ ret.add(new Temperature(
+ temperature.currentValue, temperature.type,
+ temperature.name,
+ Temperature.THROTTLING_NONE));
+ }
+ } else {
+ Slog.e(TAG,
+ "Couldn't get temperatures because of HAL error: "
+ + status.debugMessage);
+ }
+
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
+ connectToHal();
+ }
+ return ret;
+ }
+ }
+
+ @Override
+ protected boolean connectToHal() {
+ synchronized (mHalLock) {
+ try {
+ mThermalHal11 = android.hardware.thermal.V1_1.IThermal.getService();
+ mThermalHal11.linkToDeath(new DeathRecipient(),
+ THERMAL_HAL_DEATH_COOKIE);
+ mThermalHal11.registerThermalCallback(mThermalCallback11);
+ } catch (NoSuchElementException | RemoteException e) {
+ Slog.e(TAG,
+ "Thermal HAL 1.1 service not connected, no thermal call back will be "
+ + "called.");
+ mThermalHal11 = null;
+ }
+ return (mThermalHal11 != null);
+ }
+ }
+
+ @Override
+ protected void dump(PrintWriter pw, String prefix) {
+ synchronized (mHalLock) {
+ pw.print(prefix);
+ pw.println("ThermalHAL 1.1 connected: " + (mThermalHal11 != null ? "yes"
+ : "no"));
+ }
+ }
+ }
+
+ static class ThermalHal20Wrapper extends ThermalHalWrapper {
+ /** Proxy object for the Thermal HAL 2.0 service. */
+ @GuardedBy("mHalLock")
+ private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
+
+ /** HWbinder callback for Thermal HAL 2.0. */
+ private final IThermalChangedCallback.Stub mThermalCallback20 =
+ new IThermalChangedCallback.Stub() {
+ @Override
+ public void notifyThrottling(
+ android.hardware.thermal.V2_0.Temperature temperature) {
+ Temperature thermalSvcTemp = new Temperature(
+ temperature.value, temperature.type, temperature.name,
+ temperature.throttlingStatus);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallback.onValues(thermalSvcTemp);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ @Override
+ protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+ int type) {
+ synchronized (mHalLock) {
+ List<Temperature> ret = new ArrayList<>();
+ if (mThermalHal20 == null) {
+ return ret;
+ }
+ try {
+ mThermalHal20.getCurrentTemperatures(shouldFilter, type,
+ (ThermalStatus status,
+ ArrayList<android.hardware.thermal.V2_0.Temperature>
+ temperatures) -> {
+ if (ThermalStatusCode.SUCCESS == status.code) {
+ for (android.hardware.thermal.V2_0.Temperature
+ temperature : temperatures) {
+ ret.add(new Temperature(
+ temperature.value, temperature.type,
+ temperature.name,
+ temperature.throttlingStatus));
+ }
+ } else {
+ Slog.e(TAG,
+ "Couldn't get temperatures because of HAL error: "
+ + status.debugMessage);
+ }
+
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
+ connectToHal();
+ }
+ return ret;
+ }
+ }
+
+ @Override
+ protected boolean connectToHal() {
+ synchronized (mHalLock) {
+ try {
+ mThermalHal20 = android.hardware.thermal.V2_0.IThermal.getService();
+ mThermalHal20.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
+ mThermalHal20.registerThermalChangedCallback(mThermalCallback20, false,
+ 0 /* not used */);
+ } catch (NoSuchElementException | RemoteException e) {
+ Slog.e(TAG, "Thermal HAL 2.0 service not connected, trying 1.1.");
+ mThermalHal20 = null;
+ }
+ return (mThermalHal20 != null);
+ }
+ }
+
+ @Override
+ protected void dump(PrintWriter pw, String prefix) {
+ synchronized (mHalLock) {
+ pw.print(prefix);
+ pw.println("ThermalHAL 2.0 connected: " + (mThermalHal20 != null ? "yes"
+ : "no"));
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
new file mode 100644
index 0000000..7cf7df13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.IThermalEventListener;
+import android.os.IThermalStatusListener;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemService;
+import com.android.server.power.ThermalManagerService.ThermalHalWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server
+ * /power/ThermalManagerServiceTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ThermalManagerServiceTest {
+ private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
+ private ThermalManagerService mService;
+ private ThermalHalFake mFakeHal;
+ private PowerManager mPowerManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalEventListener mEventListener1;
+ @Mock
+ private IThermalEventListener mEventListener2;
+ @Mock
+ private IThermalStatusListener mStatusListener1;
+ @Mock
+ private IThermalStatusListener mStatusListener2;
+
+ /**
+ * Fake Hal class.
+ */
+ private class ThermalHalFake extends ThermalHalWrapper {
+ private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
+ private ArrayList<Temperature> mTemperatureList = new ArrayList<>();
+ private Temperature mSkin1 = new Temperature(0, Temperature.TYPE_SKIN, "skin1",
+ INIT_STATUS);
+ private Temperature mSkin2 = new Temperature(0, Temperature.TYPE_SKIN, "skin2",
+ INIT_STATUS);
+ private Temperature mBattery = new Temperature(0, Temperature.TYPE_BATTERY, "batt",
+ INIT_STATUS);
+ private Temperature mUsbPort = new Temperature(0, Temperature.TYPE_USB_PORT, "usbport",
+ INIT_STATUS);
+
+ ThermalHalFake() {
+ mTemperatureList.add(mSkin1);
+ mTemperatureList.add(mSkin2);
+ mTemperatureList.add(mBattery);
+ mTemperatureList.add(mUsbPort);
+ }
+
+ @Override
+ protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
+ return mTemperatureList;
+ }
+
+ @Override
+ protected boolean connectToHal() {
+ return true;
+ }
+
+ @Override
+ protected void dump(PrintWriter pw, String prefix) {
+ return;
+ }
+ }
+
+ private void assertTemperatureEquals(List<Temperature> expected, List<Temperature> value) {
+ assertEquals(new HashSet<>(expected), new HashSet<>(value));
+ }
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mFakeHal = new ThermalHalFake();
+ mPowerManager = new PowerManager(mContext, mIPowerManagerMock, null);
+ when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
+ when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+ resetListenerMock();
+ mService = new ThermalManagerService(mContext, mFakeHal);
+ // Register callbacks before AMS ready and no callback sent
+ assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+ Temperature.TYPE_SKIN));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(any(Temperature.class));
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(any(Temperature.class));
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(4)).notifyThrottling(captor.capture());
+ assertTemperatureEquals(mFakeHal.mTemperatureList, captor.getAllValues());
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ captor = ArgumentCaptor.forClass(Temperature.class);
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(2)).notifyThrottling(captor.capture());
+ assertTemperatureEquals(new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)),
+ captor.getAllValues());
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ }
+
+ private void resetListenerMock() {
+ reset(mEventListener1);
+ reset(mStatusListener1);
+ reset(mEventListener2);
+ reset(mStatusListener2);
+ doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
+ doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
+ doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
+ doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
+ }
+
+ @Test
+ public void testRegister() throws RemoteException {
+ // Unregister all
+ assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.unregisterThermalEventListener(mEventListener2));
+ assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener2));
+ resetListenerMock();
+ // Register callbacks and verify they are called
+ assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+ ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(4)).notifyThrottling(captor.capture());
+ assertTemperatureEquals(mFakeHal.mTemperatureList, captor.getAllValues());
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ // Register new callbacks and verify old ones are not called (remained same) while new
+ // ones are called
+ assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+ Temperature.TYPE_SKIN));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(4)).notifyThrottling(any(Temperature.class));
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ captor = ArgumentCaptor.forClass(Temperature.class);
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(2)).notifyThrottling(captor.capture());
+ assertTemperatureEquals(new ArrayList<>(Arrays.asList(mFakeHal.mSkin1, mFakeHal.mSkin2)),
+ captor.getAllValues());
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+ }
+
+ @Test
+ public void testNotify() throws RemoteException {
+ int status = Temperature.THROTTLING_SEVERE;
+ Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
+ mFakeHal.mCallback.onValues(newBattery);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newBattery);
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(status);
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(newBattery);
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(status);
+ resetListenerMock();
+ // Should only notify event not status
+ Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onValues(newSkin);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newSkin);
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newSkin);
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ resetListenerMock();
+ // Back to None, should only notify event not status
+ status = Temperature.THROTTLING_NONE;
+ newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
+ mFakeHal.mCallback.onValues(newBattery);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newBattery);
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).notifyThrottling(newBattery);
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(0)).onStatusChange(anyInt());
+ resetListenerMock();
+ // Should also notify status
+ newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onValues(newSkin);
+ verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newSkin);
+ verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(status);
+ verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).notifyThrottling(newSkin);
+ verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).onStatusChange(status);
+ }
+
+ @Test
+ public void testGetCurrentTemperatures() throws RemoteException {
+ assertTemperatureEquals(mFakeHal.getCurrentTemperatures(false, 0),
+ mService.mService.getCurrentTemperatures());
+ assertTemperatureEquals(mFakeHal.getCurrentTemperatures(true, Temperature.TYPE_SKIN),
+ mService.mService.getCurrentTemperaturesWithType(Temperature.TYPE_SKIN));
+ }
+
+ @Test
+ public void testGetCurrentStatus() throws RemoteException {
+ int status = Temperature.THROTTLING_WARNING;
+ Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onValues(newSkin);
+ assertEquals(status, mService.mService.getCurrentStatus());
+ }
+
+ @Test
+ public void testThermalShutdown() throws RemoteException {
+ int status = Temperature.THROTTLING_SHUTDOWN;
+ Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
+ mFakeHal.mCallback.onValues(newSkin);
+ verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+ .times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
+ }
+
+ @Test
+ public void testNoHal() throws RemoteException {
+ mService = new ThermalManagerService(mContext);
+ // Do no call onActivityManagerReady to skip connect HAL
+ assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+ assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
+ assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+ assertEquals(0, mService.mService.getCurrentTemperatures().size());
+ assertEquals(0,
+ mService.mService.getCurrentTemperaturesWithType(Temperature.TYPE_SKIN).size());
+ assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentStatus());
+ }
+}