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