Add thermal service into system_server
This system service will listen to ThermalHAL for throttling events and
take actions accordingly, e.g. shutdown device and/or sending
notification to registered listeners to IThermalSerivce.
Bug: 79443945
Bug: 118510237
Bug: 111086696
Bug: 116541003
Test: Boot and test callback on ThermalHAL 1.1
Test: Boot and test callback on ThermalHAL 2.0
Test: Kill ThermalHAL process
Test: Change device threshold to trigger shutdown
Change-Id: I1f4066c9f1cf9ab46c1738a0a4435802512e4339
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index d118c4e..20e4985 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,3 +2,4 @@
per-file BatterySaverPolicy.java=omakoto@google.com
per-file ShutdownThread.java=fkupolov@google.com
+per-file ThermalManagerService.java=wvw@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
new file mode 100644
index 0000000..812fd82
--- /dev/null
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -0,0 +1,392 @@
+/*
+ * 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 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.ThrottlingSeverity;
+import android.os.Binder;
+import android.os.HwBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * This is a system service that listens to HAL thermal events and dispatch those to listeners.
+ * <p>The service will also trigger actions based on severity of the throttling status.</p>
+ *
+ * @hide
+ */
+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 */
+ @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. */
+ @GuardedBy("mLock")
+ private IThermalEventListener mNewListenerCallback = null;
+
+ /** Newly registered callback type, null means not filter type. */
+ @GuardedBy("mLock")
+ private Integer mNewListenerType = null;
+
+ /** Local PMS handle. */
+ private final PowerManager mPowerManager;
+
+ /** Proxy object for the Thermal HAL 2.0 service. */
+ @GuardedBy("mLock")
+ private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
+
+ /** 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);
+ }
+ }
+ };
+
+ public ThermalManagerService(Context context) {
+ super(context);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ private void setNewListener(IThermalEventListener listener, Integer type) {
+ synchronized (mLock) {
+ mNewListenerCallback = listener;
+ mNewListenerType = type;
+ }
+ }
+
+ private void clearNewListener() {
+ 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();
+ }
+ }
+ }
+
+ @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();
+ }
+ }
+ }
+
+ @Override
+ public void unregisterThermalEventListener(IThermalEventListener listener) {
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mThermalEventListeners.unregister(listener);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @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) {
+ // Skip if listener registered with a different type
+ if (type != null && type != temperature.getType()) {
+ return;
+ }
+ final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
+ try {
+ listener.notifyThrottling(temperature);
+ } 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 notifyThrottlingImpl(android.os.Temperature temperature) {
+ synchronized (mLock) {
+ // Thermal Shutdown for Skin temperature
+ if (temperature.getStatus() == android.os.Temperature.THROTTLING_SHUTDOWN
+ && temperature.getType() == android.os.Temperature.TYPE_SKIN) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mPowerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if (mNewListenerCallback != null) {
+ // Only notify current newly added callback.
+ notifyListener(temperature, mNewListenerCallback, mNewListenerType);
+ } 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();
+ }
+ }
+ }
+ }
+
+ @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 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 {
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == THERMAL_HAL_DEATH_COOKIE) {
+ Slog.e(TAG, "Thermal HAL service died cookie: " + cookie);
+ synchronized (mLock) {
+ connectToHalLocked();
+ // Post to listeners after reconnect to HAL.
+ notifyCurrentTemperaturesLocked();
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4d3fc1a..2be55c0 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -253,8 +253,8 @@
if (b != null) {
sThermalService = IThermalService.Stub.asInterface(b);
try {
- sThermalService.registerThermalEventListener(
- new ThermalEventListener());
+ sThermalService.registerThermalEventListenerWithType(
+ new ThermalEventListener(), Temperature.TYPE_SKIN);
Slog.i(TAG, "register thermal listener successfully");
} catch (RemoteException e) {
// Should never happen.
@@ -1853,7 +1853,8 @@
// Thermal event received from vendor thermal management subsystem
private static final class ThermalEventListener extends IThermalEventListener.Stub {
@Override
- public void notifyThrottling(boolean isThrottling, Temperature temp) {
+ public void notifyThrottling(Temperature temp) {
+ boolean isThrottling = temp.getStatus() >= Temperature.THROTTLING_SEVERE;
StatsLog.write(StatsLog.THERMAL_THROTTLING, temp.getType(),
isThrottling ? 1 : 0, temp.getValue());
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 54a140d..43190ac4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -115,6 +115,7 @@
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
+import com.android.server.power.ThermalManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
@@ -607,6 +608,10 @@
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
traceEnd();
+ traceBeginAndSlog("StartThermalManager");
+ mSystemServiceManager.startService(ThermalManagerService.class);
+ traceEnd();
+
// Now that the power manager has been started, let the activity manager
// initialize power management features.
traceBeginAndSlog("InitPowerManagement");