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