• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.power;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.hardware.thermal.V1_0.ThermalStatus;
23 import android.hardware.thermal.V1_0.ThermalStatusCode;
24 import android.hardware.thermal.V1_1.IThermalCallback;
25 import android.hardware.thermal.V2_0.IThermalChangedCallback;
26 import android.hardware.thermal.V2_0.TemperatureThreshold;
27 import android.hardware.thermal.V2_0.ThrottlingSeverity;
28 import android.os.Binder;
29 import android.os.CoolingDevice;
30 import android.os.Handler;
31 import android.os.HwBinder;
32 import android.os.IThermalEventListener;
33 import android.os.IThermalService;
34 import android.os.IThermalStatusListener;
35 import android.os.PowerManager;
36 import android.os.Process;
37 import android.os.RemoteCallbackList;
38 import android.os.RemoteException;
39 import android.os.ResultReceiver;
40 import android.os.ShellCallback;
41 import android.os.ShellCommand;
42 import android.os.SystemClock;
43 import android.os.Temperature;
44 import android.util.ArrayMap;
45 import android.util.EventLog;
46 import android.util.Slog;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.os.BackgroundThread;
51 import com.android.internal.util.DumpUtils;
52 import com.android.server.EventLogTags;
53 import com.android.server.FgThread;
54 import com.android.server.SystemService;
55 
56 import java.io.FileDescriptor;
57 import java.io.PrintWriter;
58 import java.util.ArrayList;
59 import java.util.Collection;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.NoSuchElementException;
64 import java.util.concurrent.atomic.AtomicBoolean;
65 
66 /**
67  * This is a system service that listens to HAL thermal events and dispatch those to listeners.
68  * <p>The service will also trigger actions based on severity of the throttling status.</p>
69  *
70  * @hide
71  */
72 public class ThermalManagerService extends SystemService {
73     private static final String TAG = ThermalManagerService.class.getSimpleName();
74 
75     /** Lock to protect listen list. */
76     private final Object mLock = new Object();
77 
78     /**
79      * Registered observers of the thermal events. Cookie is used to store type as Integer, null
80      * means no filter.
81      */
82     @GuardedBy("mLock")
83     private final RemoteCallbackList<IThermalEventListener> mThermalEventListeners =
84             new RemoteCallbackList<>();
85 
86     /** Registered observers of the thermal status. */
87     @GuardedBy("mLock")
88     private final RemoteCallbackList<IThermalStatusListener> mThermalStatusListeners =
89             new RemoteCallbackList<>();
90 
91     /** Current thermal status */
92     @GuardedBy("mLock")
93     private int mStatus;
94 
95     /** If override status takes effect*/
96     @GuardedBy("mLock")
97     private boolean mIsStatusOverride;
98 
99     /** Current thermal map, key as name */
100     @GuardedBy("mLock")
101     private ArrayMap<String, Temperature> mTemperatureMap = new ArrayMap<>();
102 
103     /** HAL wrapper. */
104     private ThermalHalWrapper mHalWrapper;
105 
106     /** Hal ready. */
107     private final AtomicBoolean mHalReady = new AtomicBoolean();
108 
109     /** Watches temperatures to forecast when throttling will occur */
110     @VisibleForTesting
111     final TemperatureWatcher mTemperatureWatcher = new TemperatureWatcher();
112 
ThermalManagerService(Context context)113     public ThermalManagerService(Context context) {
114         this(context, null);
115     }
116 
117     @VisibleForTesting
ThermalManagerService(Context context, @Nullable ThermalHalWrapper halWrapper)118     ThermalManagerService(Context context, @Nullable ThermalHalWrapper halWrapper) {
119         super(context);
120         mHalWrapper = halWrapper;
121         mStatus = Temperature.THROTTLING_NONE;
122     }
123 
124     @Override
onStart()125     public void onStart() {
126         publishBinderService(Context.THERMAL_SERVICE, mService);
127     }
128 
129     @Override
onBootPhase(int phase)130     public void onBootPhase(int phase) {
131         if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
132             onActivityManagerReady();
133         }
134     }
135 
onActivityManagerReady()136     private void onActivityManagerReady() {
137         synchronized (mLock) {
138             // Connect to HAL and post to listeners.
139             boolean halConnected = (mHalWrapper != null);
140             if (!halConnected) {
141                 mHalWrapper = new ThermalHal20Wrapper();
142                 halConnected = mHalWrapper.connectToHal();
143             }
144             if (!halConnected) {
145                 mHalWrapper = new ThermalHal11Wrapper();
146                 halConnected = mHalWrapper.connectToHal();
147             }
148             if (!halConnected) {
149                 mHalWrapper = new ThermalHal10Wrapper();
150                 halConnected = mHalWrapper.connectToHal();
151             }
152             mHalWrapper.setCallback(this::onTemperatureChangedCallback);
153             if (!halConnected) {
154                 Slog.w(TAG, "No Thermal HAL service on this device");
155                 return;
156             }
157             List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(false,
158                     0);
159             final int count = temperatures.size();
160             if (count == 0) {
161                 Slog.w(TAG, "Thermal HAL reported invalid data, abort connection");
162             }
163             for (int i = 0; i < count; i++) {
164                 onTemperatureChanged(temperatures.get(i), false);
165             }
166             onTemperatureMapChangedLocked();
167             mTemperatureWatcher.updateSevereThresholds();
168             mHalReady.set(true);
169         }
170     }
171 
postStatusListener(IThermalStatusListener listener)172     private void postStatusListener(IThermalStatusListener listener) {
173         final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
174             try {
175                 listener.onStatusChange(mStatus);
176             } catch (RemoteException | RuntimeException e) {
177                 Slog.e(TAG, "Thermal callback failed to call", e);
178             }
179         });
180         if (!thermalCallbackQueued) {
181             Slog.e(TAG, "Thermal callback failed to queue");
182         }
183     }
184 
notifyStatusListenersLocked()185     private void notifyStatusListenersLocked() {
186         final int length = mThermalStatusListeners.beginBroadcast();
187         try {
188             for (int i = 0; i < length; i++) {
189                 final IThermalStatusListener listener =
190                         mThermalStatusListeners.getBroadcastItem(i);
191                 postStatusListener(listener);
192             }
193         } finally {
194             mThermalStatusListeners.finishBroadcast();
195         }
196     }
197 
onTemperatureMapChangedLocked()198     private void onTemperatureMapChangedLocked() {
199         int newStatus = Temperature.THROTTLING_NONE;
200         final int count = mTemperatureMap.size();
201         for (int i = 0; i < count; i++) {
202             Temperature t = mTemperatureMap.valueAt(i);
203             if (t.getType() == Temperature.TYPE_SKIN && t.getStatus() >= newStatus) {
204                 newStatus = t.getStatus();
205             }
206         }
207         // Do not update if override from shell
208         if (!mIsStatusOverride) {
209             setStatusLocked(newStatus);
210         }
211     }
212 
setStatusLocked(int newStatus)213     private void setStatusLocked(int newStatus) {
214         if (newStatus != mStatus) {
215             mStatus = newStatus;
216             notifyStatusListenersLocked();
217         }
218     }
219 
postEventListenerCurrentTemperatures(IThermalEventListener listener, @Nullable Integer type)220     private void postEventListenerCurrentTemperatures(IThermalEventListener listener,
221             @Nullable Integer type) {
222         synchronized (mLock) {
223             final int count = mTemperatureMap.size();
224             for (int i = 0; i < count; i++) {
225                 postEventListener(mTemperatureMap.valueAt(i), listener,
226                         type);
227             }
228         }
229     }
230 
postEventListener(Temperature temperature, IThermalEventListener listener, @Nullable Integer type)231     private void postEventListener(Temperature temperature,
232             IThermalEventListener listener,
233             @Nullable Integer type) {
234         // Skip if listener registered with a different type
235         if (type != null && type != temperature.getType()) {
236             return;
237         }
238         final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
239             try {
240                 listener.notifyThrottling(temperature);
241             } catch (RemoteException | RuntimeException e) {
242                 Slog.e(TAG, "Thermal callback failed to call", e);
243             }
244         });
245         if (!thermalCallbackQueued) {
246             Slog.e(TAG, "Thermal callback failed to queue");
247         }
248     }
249 
notifyEventListenersLocked(Temperature temperature)250     private void notifyEventListenersLocked(Temperature temperature) {
251         final int length = mThermalEventListeners.beginBroadcast();
252         try {
253             for (int i = 0; i < length; i++) {
254                 final IThermalEventListener listener =
255                         mThermalEventListeners.getBroadcastItem(i);
256                 final Integer type =
257                         (Integer) mThermalEventListeners.getBroadcastCookie(i);
258                 postEventListener(temperature, listener, type);
259             }
260         } finally {
261             mThermalEventListeners.finishBroadcast();
262         }
263         EventLog.writeEvent(EventLogTags.THERMAL_CHANGED, temperature.getName(),
264                 temperature.getType(), temperature.getValue(), temperature.getStatus(), mStatus);
265     }
266 
shutdownIfNeeded(Temperature temperature)267     private void shutdownIfNeeded(Temperature temperature) {
268         if (temperature.getStatus() != Temperature.THROTTLING_SHUTDOWN) {
269             return;
270         }
271         final PowerManager powerManager = getContext().getSystemService(PowerManager.class);
272         switch (temperature.getType()) {
273             case Temperature.TYPE_CPU:
274                 // Fall through
275             case Temperature.TYPE_GPU:
276                 // Fall through
277             case Temperature.TYPE_NPU:
278                 // Fall through
279             case Temperature.TYPE_SKIN:
280                 powerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
281                 break;
282             case Temperature.TYPE_BATTERY:
283                 powerManager.shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
284                 break;
285         }
286     }
287 
onTemperatureChanged(Temperature temperature, boolean sendStatus)288     private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
289         shutdownIfNeeded(temperature);
290         synchronized (mLock) {
291             Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
292             if (old == null || old.getStatus() != temperature.getStatus()) {
293                 notifyEventListenersLocked(temperature);
294             }
295             if (sendStatus) {
296                 onTemperatureMapChangedLocked();
297             }
298         }
299     }
300 
301     /* HwBinder callback **/
onTemperatureChangedCallback(Temperature temperature)302     private void onTemperatureChangedCallback(Temperature temperature) {
303         final long token = Binder.clearCallingIdentity();
304         try {
305             onTemperatureChanged(temperature, true);
306         } finally {
307             Binder.restoreCallingIdentity(token);
308         }
309     }
310 
311     @VisibleForTesting
312     final IThermalService.Stub mService = new IThermalService.Stub() {
313         @Override
314         public boolean registerThermalEventListener(IThermalEventListener listener) {
315             getContext().enforceCallingOrSelfPermission(
316                     android.Manifest.permission.DEVICE_POWER, null);
317             synchronized (mLock) {
318                 final long token = Binder.clearCallingIdentity();
319                 try {
320                     if (!mThermalEventListeners.register(listener, null)) {
321                         return false;
322                     }
323                     // Notify its callback after new client registered.
324                     postEventListenerCurrentTemperatures(listener, null);
325                     return true;
326                 } finally {
327                     Binder.restoreCallingIdentity(token);
328                 }
329             }
330         }
331 
332         @Override
333         public boolean registerThermalEventListenerWithType(IThermalEventListener listener,
334                 int type) {
335             getContext().enforceCallingOrSelfPermission(
336                     android.Manifest.permission.DEVICE_POWER, null);
337             synchronized (mLock) {
338                 final long token = Binder.clearCallingIdentity();
339                 try {
340                     if (!mThermalEventListeners.register(listener, new Integer(type))) {
341                         return false;
342                     }
343                     // Notify its callback after new client registered.
344                     postEventListenerCurrentTemperatures(listener, new Integer(type));
345                     return true;
346                 } finally {
347                     Binder.restoreCallingIdentity(token);
348                 }
349             }
350         }
351 
352         @Override
353         public boolean unregisterThermalEventListener(IThermalEventListener listener) {
354             getContext().enforceCallingOrSelfPermission(
355                     android.Manifest.permission.DEVICE_POWER, null);
356             synchronized (mLock) {
357                 final long token = Binder.clearCallingIdentity();
358                 try {
359                     return mThermalEventListeners.unregister(listener);
360                 } finally {
361                     Binder.restoreCallingIdentity(token);
362                 }
363             }
364         }
365 
366         @Override
367         public Temperature[] getCurrentTemperatures() {
368             getContext().enforceCallingOrSelfPermission(
369                     android.Manifest.permission.DEVICE_POWER, null);
370             final long token = Binder.clearCallingIdentity();
371             try {
372                 if (!mHalReady.get()) {
373                     return new Temperature[0];
374                 }
375                 final List<Temperature> curr = mHalWrapper.getCurrentTemperatures(
376                         false, 0 /* not used */);
377                 return curr.toArray(new Temperature[curr.size()]);
378             } finally {
379                 Binder.restoreCallingIdentity(token);
380             }
381         }
382 
383         @Override
384         public Temperature[] getCurrentTemperaturesWithType(int type) {
385             getContext().enforceCallingOrSelfPermission(
386                     android.Manifest.permission.DEVICE_POWER, null);
387             final long token = Binder.clearCallingIdentity();
388             try {
389                 if (!mHalReady.get()) {
390                     return new Temperature[0];
391                 }
392                 final List<Temperature> curr = mHalWrapper.getCurrentTemperatures(true, type);
393                 return curr.toArray(new Temperature[curr.size()]);
394             } finally {
395                 Binder.restoreCallingIdentity(token);
396             }
397         }
398 
399         @Override
400         public boolean registerThermalStatusListener(IThermalStatusListener listener) {
401             synchronized (mLock) {
402                 // Notify its callback after new client registered.
403                 final long token = Binder.clearCallingIdentity();
404                 try {
405                     if (!mThermalStatusListeners.register(listener)) {
406                         return false;
407                     }
408                     // Notify its callback after new client registered.
409                     postStatusListener(listener);
410                     return true;
411                 } finally {
412                     Binder.restoreCallingIdentity(token);
413                 }
414             }
415         }
416 
417         @Override
418         public boolean unregisterThermalStatusListener(IThermalStatusListener listener) {
419             synchronized (mLock) {
420                 final long token = Binder.clearCallingIdentity();
421                 try {
422                     return mThermalStatusListeners.unregister(listener);
423                 } finally {
424                     Binder.restoreCallingIdentity(token);
425                 }
426             }
427         }
428 
429         @Override
430         public int getCurrentThermalStatus() {
431             synchronized (mLock) {
432                 final long token = Binder.clearCallingIdentity();
433                 try {
434                     return mStatus;
435                 } finally {
436                     Binder.restoreCallingIdentity(token);
437                 }
438             }
439         }
440 
441         @Override
442         public CoolingDevice[] getCurrentCoolingDevices() {
443             getContext().enforceCallingOrSelfPermission(
444                     android.Manifest.permission.DEVICE_POWER, null);
445             final long token = Binder.clearCallingIdentity();
446             try {
447                 if (!mHalReady.get()) {
448                     return new CoolingDevice[0];
449                 }
450                 final List<CoolingDevice> devList = mHalWrapper.getCurrentCoolingDevices(
451                         false, 0);
452                 return devList.toArray(new CoolingDevice[devList.size()]);
453             } finally {
454                 Binder.restoreCallingIdentity(token);
455             }
456         }
457 
458         @Override
459         public CoolingDevice[] getCurrentCoolingDevicesWithType(int type) {
460             getContext().enforceCallingOrSelfPermission(
461                     android.Manifest.permission.DEVICE_POWER, null);
462             final long token = Binder.clearCallingIdentity();
463             try {
464                 if (!mHalReady.get()) {
465                     return new CoolingDevice[0];
466                 }
467                 final List<CoolingDevice> devList = mHalWrapper.getCurrentCoolingDevices(
468                         true, type);
469                 return devList.toArray(new CoolingDevice[devList.size()]);
470             } finally {
471                 Binder.restoreCallingIdentity(token);
472             }
473         }
474 
475         @Override
476         public float getThermalHeadroom(int forecastSeconds) {
477             if (!mHalReady.get()) {
478                 return Float.NaN;
479             }
480 
481             return mTemperatureWatcher.getForecast(forecastSeconds);
482         }
483 
484         private void dumpItemsLocked(PrintWriter pw, String prefix,
485                 Collection<?> items) {
486             for (Iterator iterator = items.iterator(); iterator.hasNext();) {
487                 pw.println(prefix + iterator.next().toString());
488             }
489         }
490 
491         @Override
492         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
493             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
494                 return;
495             }
496             final long token = Binder.clearCallingIdentity();
497             try {
498                 synchronized (mLock) {
499                     pw.println("IsStatusOverride: " + mIsStatusOverride);
500                     pw.println("ThermalEventListeners:");
501                     mThermalEventListeners.dump(pw, "\t");
502                     pw.println("ThermalStatusListeners:");
503                     mThermalStatusListeners.dump(pw, "\t");
504                     pw.println("Thermal Status: " + mStatus);
505                     pw.println("Cached temperatures:");
506                     dumpItemsLocked(pw, "\t", mTemperatureMap.values());
507                     pw.println("HAL Ready: " + mHalReady.get());
508                     if (mHalReady.get()) {
509                         pw.println("HAL connection:");
510                         mHalWrapper.dump(pw, "\t");
511                         pw.println("Current temperatures from HAL:");
512                         dumpItemsLocked(pw, "\t",
513                                 mHalWrapper.getCurrentTemperatures(false, 0));
514                         pw.println("Current cooling devices from HAL:");
515                         dumpItemsLocked(pw, "\t",
516                                 mHalWrapper.getCurrentCoolingDevices(false, 0));
517                         pw.println("Temperature static thresholds from HAL:");
518                         dumpItemsLocked(pw, "\t",
519                                 mHalWrapper.getTemperatureThresholds(false, 0));
520                     }
521                 }
522             } finally {
523                 Binder.restoreCallingIdentity(token);
524             }
525         }
526 
527         private boolean isCallerShell() {
528             final int callingUid = Binder.getCallingUid();
529             return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
530         }
531 
532         @Override
533         public void onShellCommand(FileDescriptor in, FileDescriptor out,
534                 FileDescriptor err, String[] args, ShellCallback callback,
535                 ResultReceiver resultReceiver) {
536             if (!isCallerShell()) {
537                 Slog.w(TAG, "Only shell is allowed to call thermalservice shell commands");
538                 return;
539             }
540             (new ThermalShellCommand()).exec(
541                     this, in, out, err, args, callback, resultReceiver);
542         }
543 
544     };
545 
546     class ThermalShellCommand extends ShellCommand {
547         @Override
onCommand(String cmd)548         public int onCommand(String cmd) {
549             switch(cmd != null ? cmd : "") {
550                 case "override-status":
551                     return runOverrideStatus();
552                 case "reset":
553                     return runReset();
554                 default:
555                     return handleDefaultCommands(cmd);
556             }
557         }
558 
runReset()559         private int runReset() {
560             final long token = Binder.clearCallingIdentity();
561             try {
562                 synchronized (mLock) {
563                     mIsStatusOverride = false;
564                     onTemperatureMapChangedLocked();
565                     return 0;
566                 }
567             } finally {
568                 Binder.restoreCallingIdentity(token);
569             }
570         }
571 
runOverrideStatus()572         private int runOverrideStatus() {
573             final long token = Binder.clearCallingIdentity();
574             try {
575                 final PrintWriter pw = getOutPrintWriter();
576                 int status;
577                 try {
578                     status = Integer.parseInt(getNextArgRequired());
579                 } catch (RuntimeException ex) {
580                     pw.println("Error: " + ex.toString());
581                     return -1;
582                 }
583                 if (!Temperature.isValidStatus(status)) {
584                     pw.println("Invalid status: " + status);
585                     return -1;
586                 }
587                 synchronized (mLock) {
588                     mIsStatusOverride = true;
589                     setStatusLocked(status);
590                 }
591                 return 0;
592             } finally {
593                 Binder.restoreCallingIdentity(token);
594             }
595         }
596 
597         @Override
onHelp()598         public void onHelp() {
599             final PrintWriter pw = getOutPrintWriter();
600             pw.println("Thermal service (thermalservice) commands:");
601             pw.println("  help");
602             pw.println("    Print this help text.");
603             pw.println("");
604             pw.println("  override-status STATUS");
605             pw.println("    sets and locks the thermal status of the device to STATUS.");
606             pw.println("    status code is defined in android.os.Temperature.");
607             pw.println("  reset");
608             pw.println("    unlocks the thermal status of the device.");
609             pw.println();
610         }
611     }
612 
613     abstract static class ThermalHalWrapper {
614         protected static final String TAG = ThermalHalWrapper.class.getSimpleName();
615 
616         /** Lock to protect HAL handle. */
617         protected final Object mHalLock = new Object();
618 
619         @FunctionalInterface
620         interface TemperatureChangedCallback {
onValues(Temperature temperature)621             void onValues(Temperature temperature);
622         }
623 
624         /** Temperature callback. */
625         protected TemperatureChangedCallback mCallback;
626 
627         /** Cookie for matching the right end point. */
628         protected static final int THERMAL_HAL_DEATH_COOKIE = 5612;
629 
630         @VisibleForTesting
setCallback(TemperatureChangedCallback cb)631         protected void setCallback(TemperatureChangedCallback cb) {
632             mCallback = cb;
633         }
634 
getCurrentTemperatures(boolean shouldFilter, int type)635         protected abstract List<Temperature> getCurrentTemperatures(boolean shouldFilter,
636                 int type);
637 
getCurrentCoolingDevices(boolean shouldFilter, int type)638         protected abstract List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
639                 int type);
640 
641         @NonNull
getTemperatureThresholds(boolean shouldFilter, int type)642         protected abstract List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
643                 int type);
644 
connectToHal()645         protected abstract boolean connectToHal();
646 
dump(PrintWriter pw, String prefix)647         protected abstract void dump(PrintWriter pw, String prefix);
648 
resendCurrentTemperatures()649         protected void resendCurrentTemperatures() {
650             synchronized (mHalLock) {
651                 List<Temperature> temperatures = getCurrentTemperatures(false, 0);
652                 final int count = temperatures.size();
653                 for (int i = 0; i < count; i++) {
654                     mCallback.onValues(temperatures.get(i));
655                 }
656             }
657         }
658 
659         final class DeathRecipient implements HwBinder.DeathRecipient {
660             @Override
serviceDied(long cookie)661             public void serviceDied(long cookie) {
662                 if (cookie == THERMAL_HAL_DEATH_COOKIE) {
663                     Slog.e(TAG, "Thermal HAL service died cookie: " + cookie);
664                     synchronized (mHalLock) {
665                         connectToHal();
666                         // Post to listeners after reconnect to HAL.
667                         resendCurrentTemperatures();
668                     }
669                 }
670             }
671         }
672     }
673 
674 
675     static class ThermalHal10Wrapper extends ThermalHalWrapper {
676         /** Proxy object for the Thermal HAL 1.0 service. */
677         @GuardedBy("mHalLock")
678         private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
679 
680         @Override
getCurrentTemperatures(boolean shouldFilter, int type)681         protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
682                 int type) {
683             synchronized (mHalLock) {
684                 List<Temperature> ret = new ArrayList<>();
685                 if (mThermalHal10 == null) {
686                     return ret;
687                 }
688                 try {
689                     mThermalHal10.getTemperatures(
690                             (ThermalStatus status,
691                                     ArrayList<android.hardware.thermal.V1_0.Temperature>
692                                             temperatures) -> {
693                                 if (ThermalStatusCode.SUCCESS == status.code) {
694                                     for (android.hardware.thermal.V1_0.Temperature
695                                             temperature : temperatures) {
696                                         if (shouldFilter && type != temperature.type) {
697                                             continue;
698                                         }
699                                         // Thermal HAL 1.0 doesn't report current throttling status
700                                         ret.add(new Temperature(
701                                                 temperature.currentValue, temperature.type,
702                                                 temperature.name,
703                                                 Temperature.THROTTLING_NONE));
704                                     }
705                                 } else {
706                                     Slog.e(TAG,
707                                             "Couldn't get temperatures because of HAL error: "
708                                                     + status.debugMessage);
709                                 }
710 
711                             });
712                 } catch (RemoteException e) {
713                     Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
714                     connectToHal();
715                 }
716                 return ret;
717             }
718         }
719 
720         @Override
getCurrentCoolingDevices(boolean shouldFilter, int type)721         protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
722                 int type) {
723             synchronized (mHalLock) {
724                 List<CoolingDevice> ret = new ArrayList<>();
725                 if (mThermalHal10 == null) {
726                     return ret;
727                 }
728                 try {
729                     mThermalHal10.getCoolingDevices((status, coolingDevices) -> {
730                         if (ThermalStatusCode.SUCCESS == status.code) {
731                             for (android.hardware.thermal.V1_0.CoolingDevice
732                                     coolingDevice : coolingDevices) {
733                                 if (shouldFilter && type != coolingDevice.type) {
734                                     continue;
735                                 }
736                                 ret.add(new CoolingDevice(
737                                         (long) coolingDevice.currentValue,
738                                         coolingDevice.type,
739                                         coolingDevice.name));
740                             }
741                         } else {
742                             Slog.e(TAG,
743                                     "Couldn't get cooling device because of HAL error: "
744                                             + status.debugMessage);
745                         }
746 
747                     });
748                 } catch (RemoteException e) {
749                     Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
750                     connectToHal();
751                 }
752                 return ret;
753             }
754         }
755 
756         @Override
getTemperatureThresholds(boolean shouldFilter, int type)757         protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
758                 int type) {
759             return new ArrayList<>();
760         }
761 
762         @Override
connectToHal()763         protected boolean connectToHal() {
764             synchronized (mHalLock) {
765                 try {
766                     mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService(true);
767                     mThermalHal10.linkToDeath(new DeathRecipient(),
768                             THERMAL_HAL_DEATH_COOKIE);
769                     Slog.i(TAG,
770                             "Thermal HAL 1.0 service connected, no thermal call back will be "
771                                     + "called due to legacy API.");
772                 } catch (NoSuchElementException | RemoteException e) {
773                     Slog.e(TAG,
774                             "Thermal HAL 1.0 service not connected.");
775                     mThermalHal10 = null;
776                 }
777                 return (mThermalHal10 != null);
778             }
779         }
780 
781         @Override
dump(PrintWriter pw, String prefix)782         protected void dump(PrintWriter pw, String prefix) {
783             synchronized (mHalLock) {
784                 pw.print(prefix);
785                 pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes"
786                         : "no"));
787             }
788         }
789     }
790 
791     static class ThermalHal11Wrapper extends ThermalHalWrapper {
792         /** Proxy object for the Thermal HAL 1.1 service. */
793         @GuardedBy("mHalLock")
794         private android.hardware.thermal.V1_1.IThermal mThermalHal11 = null;
795 
796         /** HWbinder callback for Thermal HAL 1.1. */
797         private final IThermalCallback.Stub mThermalCallback11 =
798                 new IThermalCallback.Stub() {
799                     @Override
800                     public void notifyThrottling(boolean isThrottling,
801                             android.hardware.thermal.V1_0.Temperature temperature) {
802                         Temperature thermalSvcTemp = new Temperature(
803                                 temperature.currentValue, temperature.type, temperature.name,
804                                 isThrottling ? ThrottlingSeverity.SEVERE
805                                         : ThrottlingSeverity.NONE);
806                         final long token = Binder.clearCallingIdentity();
807                         try {
808                             mCallback.onValues(thermalSvcTemp);
809                         } finally {
810                             Binder.restoreCallingIdentity(token);
811                         }
812                     }
813                 };
814 
815         @Override
getCurrentTemperatures(boolean shouldFilter, int type)816         protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
817                 int type) {
818             synchronized (mHalLock) {
819                 List<Temperature> ret = new ArrayList<>();
820                 if (mThermalHal11 == null) {
821                     return ret;
822                 }
823                 try {
824                     mThermalHal11.getTemperatures(
825                             (ThermalStatus status,
826                                     ArrayList<android.hardware.thermal.V1_0.Temperature>
827                                             temperatures) -> {
828                                 if (ThermalStatusCode.SUCCESS == status.code) {
829                                     for (android.hardware.thermal.V1_0.Temperature
830                                             temperature : temperatures) {
831                                         if (shouldFilter && type != temperature.type) {
832                                             continue;
833                                         }
834                                         // Thermal HAL 1.1 doesn't report current throttling status
835                                         ret.add(new Temperature(
836                                                 temperature.currentValue, temperature.type,
837                                                 temperature.name,
838                                                 Temperature.THROTTLING_NONE));
839                                     }
840                                 } else {
841                                     Slog.e(TAG,
842                                             "Couldn't get temperatures because of HAL error: "
843                                                     + status.debugMessage);
844                                 }
845 
846                             });
847                 } catch (RemoteException e) {
848                     Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
849                     connectToHal();
850                 }
851                 return ret;
852             }
853         }
854 
855         @Override
getCurrentCoolingDevices(boolean shouldFilter, int type)856         protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
857                 int type) {
858             synchronized (mHalLock) {
859                 List<CoolingDevice> ret = new ArrayList<>();
860                 if (mThermalHal11 == null) {
861                     return ret;
862                 }
863                 try {
864                     mThermalHal11.getCoolingDevices((status, coolingDevices) -> {
865                         if (ThermalStatusCode.SUCCESS == status.code) {
866                             for (android.hardware.thermal.V1_0.CoolingDevice
867                                     coolingDevice : coolingDevices) {
868                                 if (shouldFilter && type != coolingDevice.type) {
869                                     continue;
870                                 }
871                                 ret.add(new CoolingDevice(
872                                         (long) coolingDevice.currentValue,
873                                         coolingDevice.type,
874                                         coolingDevice.name));
875                             }
876                         } else {
877                             Slog.e(TAG,
878                                     "Couldn't get cooling device because of HAL error: "
879                                             + status.debugMessage);
880                         }
881 
882                     });
883                 } catch (RemoteException e) {
884                     Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
885                     connectToHal();
886                 }
887                 return ret;
888             }
889         }
890 
891         @Override
getTemperatureThresholds(boolean shouldFilter, int type)892         protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
893                 int type) {
894             return new ArrayList<>();
895         }
896 
897         @Override
connectToHal()898         protected boolean connectToHal() {
899             synchronized (mHalLock) {
900                 try {
901                     mThermalHal11 = android.hardware.thermal.V1_1.IThermal.getService(true);
902                     mThermalHal11.linkToDeath(new DeathRecipient(),
903                             THERMAL_HAL_DEATH_COOKIE);
904                     mThermalHal11.registerThermalCallback(mThermalCallback11);
905                     Slog.i(TAG, "Thermal HAL 1.1 service connected, limited thermal functions "
906                             + "due to legacy API.");
907                 } catch (NoSuchElementException | RemoteException e) {
908                     Slog.e(TAG, "Thermal HAL 1.1 service not connected.");
909                     mThermalHal11 = null;
910                 }
911                 return (mThermalHal11 != null);
912             }
913         }
914 
915         @Override
dump(PrintWriter pw, String prefix)916         protected void dump(PrintWriter pw, String prefix) {
917             synchronized (mHalLock) {
918                 pw.print(prefix);
919                 pw.println("ThermalHAL 1.1 connected: " + (mThermalHal11 != null ? "yes"
920                         : "no"));
921             }
922         }
923     }
924 
925     static class ThermalHal20Wrapper extends ThermalHalWrapper {
926         /** Proxy object for the Thermal HAL 2.0 service. */
927         @GuardedBy("mHalLock")
928         private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
929 
930         /** HWbinder callback for Thermal HAL 2.0. */
931         private final IThermalChangedCallback.Stub mThermalCallback20 =
932                 new IThermalChangedCallback.Stub() {
933                     @Override
934                     public void notifyThrottling(
935                             android.hardware.thermal.V2_0.Temperature temperature) {
936                         Temperature thermalSvcTemp = new Temperature(
937                                 temperature.value, temperature.type, temperature.name,
938                                 temperature.throttlingStatus);
939                         final long token = Binder.clearCallingIdentity();
940                         try {
941                             mCallback.onValues(thermalSvcTemp);
942                         } finally {
943                             Binder.restoreCallingIdentity(token);
944                         }
945                     }
946                 };
947 
948         @Override
getCurrentTemperatures(boolean shouldFilter, int type)949         protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
950                 int type) {
951             synchronized (mHalLock) {
952                 List<Temperature> ret = new ArrayList<>();
953                 if (mThermalHal20 == null) {
954                     return ret;
955                 }
956                 try {
957                     mThermalHal20.getCurrentTemperatures(shouldFilter, type,
958                             (status, temperatures) -> {
959                                 if (ThermalStatusCode.SUCCESS == status.code) {
960                                     for (android.hardware.thermal.V2_0.Temperature
961                                             temperature : temperatures) {
962                                         if (!Temperature.isValidStatus(
963                                                 temperature.throttlingStatus)) {
964                                             Slog.e(TAG, "Invalid status data from HAL");
965                                             temperature.throttlingStatus =
966                                                 Temperature.THROTTLING_NONE;
967                                         }
968                                         ret.add(new Temperature(
969                                                 temperature.value, temperature.type,
970                                                 temperature.name,
971                                                 temperature.throttlingStatus));
972                                     }
973                                 } else {
974                                     Slog.e(TAG,
975                                             "Couldn't get temperatures because of HAL error: "
976                                                     + status.debugMessage);
977                                 }
978 
979                             });
980                 } catch (RemoteException e) {
981                     Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
982                     connectToHal();
983                 }
984                 return ret;
985             }
986         }
987 
988         @Override
getCurrentCoolingDevices(boolean shouldFilter, int type)989         protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
990                 int type) {
991             synchronized (mHalLock) {
992                 List<CoolingDevice> ret = new ArrayList<>();
993                 if (mThermalHal20 == null) {
994                     return ret;
995                 }
996                 try {
997                     mThermalHal20.getCurrentCoolingDevices(shouldFilter, type,
998                             (status, coolingDevices) -> {
999                                 if (ThermalStatusCode.SUCCESS == status.code) {
1000                                     for (android.hardware.thermal.V2_0.CoolingDevice
1001                                             coolingDevice : coolingDevices) {
1002                                         ret.add(new CoolingDevice(
1003                                                 coolingDevice.value, coolingDevice.type,
1004                                                 coolingDevice.name));
1005                                     }
1006                                 } else {
1007                                     Slog.e(TAG,
1008                                             "Couldn't get cooling device because of HAL error: "
1009                                                     + status.debugMessage);
1010                                 }
1011 
1012                             });
1013                 } catch (RemoteException e) {
1014                     Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
1015                     connectToHal();
1016                 }
1017                 return ret;
1018             }
1019         }
1020 
1021         @Override
getTemperatureThresholds(boolean shouldFilter, int type)1022         protected List<TemperatureThreshold> getTemperatureThresholds(boolean shouldFilter,
1023                 int type) {
1024             synchronized (mHalLock) {
1025                 List<TemperatureThreshold> ret = new ArrayList<>();
1026                 if (mThermalHal20 == null) {
1027                     return ret;
1028                 }
1029                 try {
1030                     mThermalHal20.getTemperatureThresholds(shouldFilter, type,
1031                             (status, thresholds) -> {
1032                                 if (ThermalStatusCode.SUCCESS == status.code) {
1033                                     ret.addAll(thresholds);
1034                                 } else {
1035                                     Slog.e(TAG,
1036                                             "Couldn't get temperature thresholds because of HAL "
1037                                                     + "error: " + status.debugMessage);
1038                                 }
1039                             });
1040                 } catch (RemoteException e) {
1041                     Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
1042                 }
1043                 return ret;
1044             }
1045         }
1046 
1047         @Override
connectToHal()1048         protected boolean connectToHal() {
1049             synchronized (mHalLock) {
1050                 try {
1051                     mThermalHal20 = android.hardware.thermal.V2_0.IThermal.getService(true);
1052                     mThermalHal20.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
1053                     mThermalHal20.registerThermalChangedCallback(mThermalCallback20, false,
1054                             0 /* not used */);
1055                     Slog.i(TAG, "Thermal HAL 2.0 service connected.");
1056                 } catch (NoSuchElementException | RemoteException e) {
1057                     Slog.e(TAG, "Thermal HAL 2.0 service not connected.");
1058                     mThermalHal20 = null;
1059                 }
1060                 return (mThermalHal20 != null);
1061             }
1062         }
1063 
1064         @Override
dump(PrintWriter pw, String prefix)1065         protected void dump(PrintWriter pw, String prefix) {
1066             synchronized (mHalLock) {
1067                 pw.print(prefix);
1068                 pw.println("ThermalHAL 2.0 connected: " + (mThermalHal20 != null ? "yes"
1069                         : "no"));
1070             }
1071         }
1072     }
1073 
1074     @VisibleForTesting
1075     class TemperatureWatcher {
1076         private final Handler mHandler = BackgroundThread.getHandler();
1077 
1078         /** Map of skin temperature sensor name to a corresponding list of samples */
1079         @GuardedBy("mSamples")
1080         @VisibleForTesting
1081         final ArrayMap<String, ArrayList<Sample>> mSamples = new ArrayMap<>();
1082 
1083         /** Map of skin temperature sensor name to the corresponding SEVERE temperature threshold */
1084         @GuardedBy("mSamples")
1085         @VisibleForTesting
1086         ArrayMap<String, Float> mSevereThresholds = new ArrayMap<>();
1087 
1088         @GuardedBy("mSamples")
1089         private long mLastForecastCallTimeMillis = 0;
1090 
updateSevereThresholds()1091         void updateSevereThresholds() {
1092             synchronized (mSamples) {
1093                 List<TemperatureThreshold> thresholds =
1094                         mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
1095                 for (int t = 0; t < thresholds.size(); ++t) {
1096                     TemperatureThreshold threshold = thresholds.get(t);
1097                     if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) {
1098                         continue;
1099                     }
1100                     float temperature =
1101                             threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE];
1102                     if (!Float.isNaN(temperature)) {
1103                         mSevereThresholds.put(threshold.name,
1104                                 threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]);
1105                     }
1106                 }
1107             }
1108         }
1109 
1110         private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
1111         private static final int RING_BUFFER_SIZE = 30;
1112 
updateTemperature()1113         private void updateTemperature() {
1114             synchronized (mSamples) {
1115                 if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
1116                         < INACTIVITY_THRESHOLD_MILLIS) {
1117                     // Trigger this again after a second as long as forecast has been called more
1118                     // recently than the inactivity timeout
1119                     mHandler.postDelayed(this::updateTemperature, 1000);
1120                 } else {
1121                     // Otherwise, we've been idle for at least 10 seconds, so we should
1122                     // shut down
1123                     mSamples.clear();
1124                     return;
1125                 }
1126 
1127                 long now = SystemClock.elapsedRealtime();
1128                 List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
1129                         Temperature.TYPE_SKIN);
1130 
1131                 for (int t = 0; t < temperatures.size(); ++t) {
1132                     Temperature temperature = temperatures.get(t);
1133 
1134                     // Filter out invalid temperatures. If this results in no values being stored at
1135                     // all, the mSamples.empty() check in getForecast() will catch it.
1136                     if (Float.isNaN(temperature.getValue())) {
1137                         continue;
1138                     }
1139 
1140                     ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
1141                             k -> new ArrayList<>(RING_BUFFER_SIZE));
1142                     if (samples.size() == RING_BUFFER_SIZE) {
1143                         samples.remove(0);
1144                     }
1145                     samples.add(new Sample(now, temperature.getValue()));
1146                 }
1147             }
1148         }
1149 
1150         /**
1151          * Calculates the trend using a linear regression. As the samples are degrees Celsius with
1152          * associated timestamps in milliseconds, the slope is in degrees Celsius per millisecond.
1153          */
1154         @VisibleForTesting
getSlopeOf(List<Sample> samples)1155         float getSlopeOf(List<Sample> samples) {
1156             long sumTimes = 0L;
1157             float sumTemperatures = 0.0f;
1158             for (int s = 0; s < samples.size(); ++s) {
1159                 Sample sample = samples.get(s);
1160                 sumTimes += sample.time;
1161                 sumTemperatures += sample.temperature;
1162             }
1163             long meanTime = sumTimes / samples.size();
1164             float meanTemperature = sumTemperatures / samples.size();
1165 
1166             long sampleVariance = 0L;
1167             float sampleCovariance = 0.0f;
1168             for (int s = 0; s < samples.size(); ++s) {
1169                 Sample sample = samples.get(s);
1170                 long timeDelta = sample.time - meanTime;
1171                 float temperatureDelta = sample.temperature - meanTemperature;
1172                 sampleVariance += timeDelta * timeDelta;
1173                 sampleCovariance += timeDelta * temperatureDelta;
1174             }
1175 
1176             return sampleCovariance / sampleVariance;
1177         }
1178 
1179         /**
1180          * Used to determine the temperature corresponding to 0.0. Given that 1.0 is pinned at the
1181          * temperature corresponding to the SEVERE threshold, we set 0.0 to be that temperature
1182          * minus DEGREES_BETWEEN_ZERO_AND_ONE.
1183          */
1184         private static final float DEGREES_BETWEEN_ZERO_AND_ONE = 30.0f;
1185 
1186         @VisibleForTesting
normalizeTemperature(float temperature, float severeThreshold)1187         float normalizeTemperature(float temperature, float severeThreshold) {
1188             synchronized (mSamples) {
1189                 float zeroNormalized = severeThreshold - DEGREES_BETWEEN_ZERO_AND_ONE;
1190                 if (temperature <= zeroNormalized) {
1191                     return 0.0f;
1192                 }
1193                 float delta = temperature - zeroNormalized;
1194                 return delta / DEGREES_BETWEEN_ZERO_AND_ONE;
1195             }
1196         }
1197 
1198         private static final int MINIMUM_SAMPLE_COUNT = 3;
1199 
getForecast(int forecastSeconds)1200         float getForecast(int forecastSeconds) {
1201             synchronized (mSamples) {
1202                 mLastForecastCallTimeMillis = System.currentTimeMillis();
1203                 if (mSamples.isEmpty()) {
1204                     updateTemperature();
1205                 }
1206 
1207                 // If somehow things take much longer than expected or there are no temperatures
1208                 // to sample, return early
1209                 if (mSamples.isEmpty()) {
1210                     Slog.e(TAG, "No temperature samples found");
1211                     return Float.NaN;
1212                 }
1213 
1214                 // If we don't have any thresholds, we can't normalize the temperatures,
1215                 // so return early
1216                 if (mSevereThresholds.isEmpty()) {
1217                     Slog.e(TAG, "No temperature thresholds found");
1218                     return Float.NaN;
1219                 }
1220 
1221                 float maxNormalized = Float.NaN;
1222                 for (Map.Entry<String, ArrayList<Sample>> entry : mSamples.entrySet()) {
1223                     String name = entry.getKey();
1224                     ArrayList<Sample> samples = entry.getValue();
1225 
1226                     Float threshold = mSevereThresholds.get(name);
1227                     if (threshold == null) {
1228                         Slog.e(TAG, "No threshold found for " + name);
1229                         continue;
1230                     }
1231 
1232                     float currentTemperature = samples.get(0).temperature;
1233 
1234                     if (samples.size() < MINIMUM_SAMPLE_COUNT) {
1235                         // Don't try to forecast, just use the latest one we have
1236                         float normalized = normalizeTemperature(currentTemperature, threshold);
1237                         if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
1238                             maxNormalized = normalized;
1239                         }
1240                         continue;
1241                     }
1242 
1243                     float slope = getSlopeOf(samples);
1244                     float normalized = normalizeTemperature(
1245                             currentTemperature + slope * forecastSeconds * 1000, threshold);
1246                     if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
1247                         maxNormalized = normalized;
1248                     }
1249                 }
1250 
1251                 return maxNormalized;
1252             }
1253         }
1254 
1255         @VisibleForTesting
1256         // Since Sample is inside an inner class, we can't make it static
1257         // This allows test code to create Sample objects via ThermalManagerService
createSampleForTesting(long time, float temperature)1258         Sample createSampleForTesting(long time, float temperature) {
1259             return new Sample(time, temperature);
1260         }
1261 
1262         @VisibleForTesting
1263         class Sample {
1264             public long time;
1265             public float temperature;
1266 
Sample(long time, float temperature)1267             Sample(long time, float temperature) {
1268                 this.time = time;
1269                 this.temperature = temperature;
1270             }
1271         }
1272     }
1273 }
1274