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