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