content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.power;
  
  import android.annotation.Nullable;
  import android.content.Context;
  import android.hardware.thermal.V1_0.ThermalStatus;
  import android.hardware.thermal.V1_0.ThermalStatusCode;
  import android.hardware.thermal.V1_1.IThermalCallback;
  import android.hardware.thermal.V2_0.IThermalChangedCallback;
  import android.hardware.thermal.V2_0.ThrottlingSeverity;
  import android.os.Binder;
  import android.os.CoolingDevice;
  import android.os.HwBinder;
  import android.os.IThermalEventListener;
  import android.os.IThermalService;
  import android.os.IThermalStatusListener;
  import android.os.PowerManager;
  import android.os.Process;
  import android.os.RemoteCallbackList;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.ShellCallback;
  import android.os.ShellCommand;
  import android.os.Temperature;
  import android.util.ArrayMap;
  import android.util.EventLog;
  import android.util.Slog;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.DumpUtils;
  import com.android.server.EventLogTags;
  import com.android.server.FgThread;
  import com.android.server.SystemService;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;
  import java.util.NoSuchElementException;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  /**
   * This is a system service that listens to HAL thermal events and dispatch those to listeners.
   * <p>The service will also trigger actions based on severity of the throttling status.</p>
   *
   * @hide
   */
  public class ThermalManagerService extends SystemService {
      private static final String TAG = ThermalManagerService.class.getSimpleName();
  
      /** Lock to protect listen list. */
      private final Object mLock = new Object();
  
      /**
       * Registered observers of the thermal events. Cookie is used to store type as Integer, null
       * means no filter.
       */
      @GuardedBy("mLock")
      private final RemoteCallbackList<IThermalEventListener> mThermalEventListeners =
              new RemoteCallbackList<>();
  
      /** Registered observers of the thermal status. */
      @GuardedBy("mLock")
      private final RemoteCallbackList<IThermalStatusListener> mThermalStatusListeners =
              new RemoteCallbackList<>();
  
      /** Current thermal status */
      @GuardedBy("mLock")
      private int mStatus;
  
      /** If override status takes effect*/
      @GuardedBy("mLock")
      private boolean mIsStatusOverride;
  
      /** Current thermal map, key as name */
      @GuardedBy("mLock")
      private ArrayMap<String, Temperature> mTemperatureMap = new ArrayMap<>();
  
      /** HAL wrapper. */
      private ThermalHalWrapper mHalWrapper;
  
      /** Hal ready. */
      private final AtomicBoolean mHalReady = new AtomicBoolean();
  
      /** Invalid throttling status */
      private static final int INVALID_THROTTLING = Integer.MIN_VALUE;
  
      public ThermalManagerService(Context context) {
          this(context, null);
      }
  
      @VisibleForTesting
      ThermalManagerService(Context context, @Nullable ThermalHalWrapper halWrapper) {
          super(context);
          mHalWrapper = halWrapper;
          // Initialize to invalid to send status onActivityManagerReady
          mStatus = INVALID_THROTTLING;
      }
  
      @Override
      public void onStart() {
          publishBinderService(Context.THERMAL_SERVICE, mService);
      }
  
      @Override
      public void onBootPhase(int phase) {
          if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
              onActivityManagerReady();
          }
      }
  
      private void onActivityManagerReady() {
          synchronized (mLock) {
              // Connect to HAL and post to listeners.
              boolean halConnected = (mHalWrapper != null);
              if (!halConnected) {
                  mHalWrapper = new ThermalHal20Wrapper();
                  halConnected = mHalWrapper.connectToHal();
              }
              if (!halConnected) {
                  mHalWrapper = new ThermalHal11Wrapper();
                  halConnected = mHalWrapper.connectToHal();
              }
              if (!halConnected) {
                  mHalWrapper = new ThermalHal10Wrapper();
                  halConnected = mHalWrapper.connectToHal();
              }
              mHalWrapper.setCallback(this::onTemperatureChangedCallback);
              if (!halConnected) {
                  return;
              }
              List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(false,
                      0);
              final int count = temperatures.size();
              for (int i = 0; i < count; i++) {
                  onTemperatureChanged(temperatures.get(i), false);
              }
              onTemperatureMapChangedLocked();
              mHalReady.set(true);
          }
      }
  
      private void postStatusListener(IThermalStatusListener listener) {
          final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
              try {
                  listener.onStatusChange(mStatus);
              } catch (RemoteException | RuntimeException e) {
                  Slog.e(TAG, "Thermal callback failed to call", e);
              }
          });
          if (!thermalCallbackQueued) {
              Slog.e(TAG, "Thermal callback failed to queue");
          }
      }
  
      private void notifyStatusListenersLocked() {
          if (!Temperature.isValidStatus(mStatus)) {
              return;
          }
          final int length = mThermalStatusListeners.beginBroadcast();
          try {
              for (int i = 0; i < length; i++) {
                  final IThermalStatusListener listener =
                          mThermalStatusListeners.getBroadcastItem(i);
                  postStatusListener(listener);
              }
          } finally {
              mThermalStatusListeners.finishBroadcast();
          }
      }
  
      private void onTemperatureMapChangedLocked() {
          int newStatus = INVALID_THROTTLING;
          final int count = mTemperatureMap.size();
          for (int i = 0; i < count; i++) {
              Temperature t = mTemperatureMap.valueAt(i);
              if (t.getStatus() >= newStatus) {
                  newStatus = t.getStatus();
              }
          }
          // Do not update if override from shell
          if (!mIsStatusOverride) {
              setStatusLocked(newStatus);
          }
      }
  
      private void setStatusLocked(int newStatus) {
          if (newStatus != mStatus) {
              mStatus = newStatus;
              notifyStatusListenersLocked();
          }
      }
  
      private void postEventListenerCurrentTemperatures(IThermalEventListener listener,
              @Nullable Integer type) {
          synchronized (mLock) {
              final int count = mTemperatureMap.size();
              for (int i = 0; i < count; i++) {
                  postEventListener(mTemperatureMap.valueAt(i), listener,
                          type);
              }
          }
      }
  
      private void postEventListener(Temperature temperature,
              IThermalEventListener listener,
              @Nullable Integer type) {
          // Skip if listener registered with a different type
          if (type != null && type != temperature.getType()) {
              return;
          }
          final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
              try {
                  listener.notifyThrottling(temperature);
              } catch (RemoteException | RuntimeException e) {
                  Slog.e(TAG, "Thermal callback failed to call", e);
              }
          });
          if (!thermalCallbackQueued) {
              Slog.e(TAG, "Thermal callback failed to queue");
          }
      }
  
      private void notifyEventListenersLocked(Temperature temperature) {
          final int length = mThermalEventListeners.beginBroadcast();
          try {
              for (int i = 0; i < length; i++) {
                  final IThermalEventListener listener =
                          mThermalEventListeners.getBroadcastItem(i);
                  final Integer type =
                          (Integer) mThermalEventListeners.getBroadcastCookie(i);
                  postEventListener(temperature, listener, type);
              }
          } finally {
              mThermalEventListeners.finishBroadcast();
          }
          EventLog.writeEvent(EventLogTags.THERMAL_CHANGED, temperature.getName(),
                  temperature.getType(), temperature.getValue(), temperature.getStatus(), mStatus);
      }
  
      private void shutdownIfNeeded(Temperature temperature) {
          if (temperature.getStatus() != Temperature.THROTTLING_SHUTDOWN) {
              return;
          }
          final PowerManager powerManager = getContext().getSystemService(PowerManager.class);
          switch (temperature.getType()) {
              case Temperature.TYPE_CPU:
                  // Fall through
              case Temperature.TYPE_GPU:
                  // Fall through
              case Temperature.TYPE_NPU:
                  // Fall through
              case Temperature.TYPE_SKIN:
                  powerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false);
                  break;
              case Temperature.TYPE_BATTERY:
                  powerManager.shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false);
                  break;
          }
      }
  
      private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
          shutdownIfNeeded(temperature);
          synchronized (mLock) {
              Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
              if (old != null) {
                  if (old.getStatus() != temperature.getStatus()) {
                      notifyEventListenersLocked(temperature);
                  }
              } else {
                  notifyEventListenersLocked(temperature);
              }
              if (sendStatus) {
                  onTemperatureMapChangedLocked();
              }
          }
      }
  
      /* HwBinder callback **/
      private void onTemperatureChangedCallback(Temperature temperature) {
          final long token = Binder.clearCallingIdentity();
          try {
              onTemperatureChanged(temperature, true);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      @VisibleForTesting
      final IThermalService.Stub mService = new IThermalService.Stub() {
          @Override
          public boolean registerThermalEventListener(IThermalEventListener listener) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              synchronized (mLock) {
                  final long token = Binder.clearCallingIdentity();
                  try {
                      if (!mThermalEventListeners.register(listener, null)) {
                          return false;
                      }
                      // Notify its callback after new client registered.
                      postEventListenerCurrentTemperatures(listener, null);
                      return true;
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public boolean registerThermalEventListenerWithType(IThermalEventListener listener,
                  int type) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              synchronized (mLock) {
                  final long token = Binder.clearCallingIdentity();
                  try {
                      if (!mThermalEventListeners.register(listener, new Integer(type))) {
                          return false;
                      }
                      // Notify its callback after new client registered.
                      postEventListenerCurrentTemperatures(listener, new Integer(type));
                      return true;
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public boolean unregisterThermalEventListener(IThermalEventListener listener) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              synchronized (mLock) {
                  final long token = Binder.clearCallingIdentity();
                  try {
                      return mThermalEventListeners.unregister(listener);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public List<Temperature> getCurrentTemperatures() {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              final long token = Binder.clearCallingIdentity();
              try {
                  if (!mHalReady.get()) {
                      return new ArrayList<>();
                  }
                  return mHalWrapper.getCurrentTemperatures(false, 0 /* not used */);
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          @Override
          public List<Temperature> getCurrentTemperaturesWithType(int type) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              final long token = Binder.clearCallingIdentity();
              try {
                  if (!mHalReady.get()) {
                      return new ArrayList<>();
                  }
                  return mHalWrapper.getCurrentTemperatures(true, type);
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          @Override
          public boolean registerThermalStatusListener(IThermalStatusListener listener) {
              synchronized (mLock) {
                  // Notify its callback after new client registered.
                  final long token = Binder.clearCallingIdentity();
                  try {
                      if (!mThermalStatusListeners.register(listener)) {
                          return false;
                      }
                      // Notify its callback after new client registered.
                      postStatusListener(listener);
                      return true;
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public boolean unregisterThermalStatusListener(IThermalStatusListener listener) {
              synchronized (mLock) {
                  final long token = Binder.clearCallingIdentity();
                  try {
                      return mThermalStatusListeners.unregister(listener);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public int getCurrentThermalStatus() {
              synchronized (mLock) {
                  final long token = Binder.clearCallingIdentity();
                  try {
                      return Temperature.isValidStatus(mStatus) ? mStatus
                              : Temperature.THROTTLING_NONE;
                  } finally {
                      Binder.restoreCallingIdentity(token);
                  }
              }
          }
  
          @Override
          public List<CoolingDevice> getCurrentCoolingDevices() {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              final long token = Binder.clearCallingIdentity();
              try {
                  if (!mHalReady.get()) {
                      return new ArrayList<>();
                  }
                  return mHalWrapper.getCurrentCoolingDevices(false, 0);
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          @Override
          public List<CoolingDevice> getCurrentCoolingDevicesWithType(int type) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.DEVICE_POWER, null);
              final long token = Binder.clearCallingIdentity();
              try {
                  if (!mHalReady.get()) {
                      return new ArrayList<>();
                  }
                  return mHalWrapper.getCurrentCoolingDevices(true, type);
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          private void dumpItemsLocked(PrintWriter pw, String prefix,
                  Collection<?> items) {
              for (Iterator iterator = items.iterator(); iterator.hasNext();) {
                  pw.println(prefix + iterator.next().toString());
              }
          }
  
          @Override
          public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
              if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                  return;
              }
              final long token = Binder.clearCallingIdentity();
              try {
                  synchronized (mLock) {
                      pw.println("IsStatusOverride: " + mIsStatusOverride);
                      pw.println("ThermalEventListeners:");
                      mThermalEventListeners.dump(pw, "\t");
                      pw.println("ThermalStatusListeners:");
                      mThermalStatusListeners.dump(pw, "\t");
                      pw.println("Thermal Status: " + mStatus);
                      pw.println("Cached temperatures:");
                      dumpItemsLocked(pw, "\t", mTemperatureMap.values());
                      pw.println("HAL Ready: " + mHalReady.get());
                      if (mHalReady.get()) {
                          pw.println("HAL connection:");
                          mHalWrapper.dump(pw, "\t");
                          pw.println("Current temperatures from HAL:");
                          dumpItemsLocked(pw, "\t",
                                  mHalWrapper.getCurrentTemperatures(false, 0));
                          pw.println("Current cooling devices from HAL:");
                          dumpItemsLocked(pw, "\t",
                                  mHalWrapper.getCurrentCoolingDevices(false, 0));
                      }
                  }
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          private boolean isCallerShell() {
              final int callingUid = Binder.getCallingUid();
              return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
          }
  
          @Override
          public void onShellCommand(FileDescriptor in, FileDescriptor out,
                  FileDescriptor err, String[] args, ShellCallback callback,
                  ResultReceiver resultReceiver) {
              if (!isCallerShell()) {
                  Slog.w(TAG, "Only shell is allowed to call thermalservice shell commands");
                  return;
              }
              (new ThermalShellCommand()).exec(
                      this, in, out, err, args, callback, resultReceiver);
          }
  
      };
  
      class ThermalShellCommand extends ShellCommand {
          @Override
          public int onCommand(String cmd) {
              switch(cmd != null ? cmd : "") {
                  case "override-status":
                      return runOverrideStatus();
                  case "reset":
                      return runReset();
                  default:
                      return handleDefaultCommands(cmd);
              }
          }
  
          private int runReset() {
              final long token = Binder.clearCallingIdentity();
              try {
                  synchronized (mLock) {
                      mIsStatusOverride = false;
                      onTemperatureMapChangedLocked();
                      return 0;
                  }
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          private int runOverrideStatus() {
              final long token = Binder.clearCallingIdentity();
              try {
                  final PrintWriter pw = getOutPrintWriter();
                  int status;
                  try {
                      status = Integer.parseInt(getNextArgRequired());
                  } catch (RuntimeException ex) {
                      pw.println("Error: " + ex.toString());
                      return -1;
                  }
                  if (!Temperature.isValidStatus(status)) {
                      pw.println("Invalid status: " + status);
                      return -1;
                  }
                  synchronized (mLock) {
                      mIsStatusOverride = true;
                      setStatusLocked(status);
                  }
                  return 0;
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
  
          @Override
          public void onHelp() {
              final PrintWriter pw = getOutPrintWriter();
              pw.println("Thermal service (thermalservice) commands:");
              pw.println("  help");
              pw.println("    Print this help text.");
              pw.println("");
              pw.println("  override-status STATUS");
              pw.println("    sets and locks the thermal status of the device to STATUS.");
              pw.println("    status code is defined in android.os.Temperature.");
              pw.println("  reset");
              pw.println("    unlocks the thermal status of the device.");
              pw.println();
          }
      }
  
      abstract static class ThermalHalWrapper {
          protected static final String TAG = ThermalHalWrapper.class.getSimpleName();
  
          /** Lock to protect HAL handle. */
          protected final Object mHalLock = new Object();
  
          @FunctionalInterface
          interface TemperatureChangedCallback {
              void onValues(Temperature temperature);
          }
  
          /** Temperature callback. */
          protected TemperatureChangedCallback mCallback;
  
          /** Cookie for matching the right end point. */
          protected static final int THERMAL_HAL_DEATH_COOKIE = 5612;
  
          @VisibleForTesting
          protected void setCallback(TemperatureChangedCallback cb) {
              mCallback = cb;
          }
  
          protected abstract List<Temperature> getCurrentTemperatures(boolean shouldFilter,
                  int type);
  
          protected abstract List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
                  int type);
  
          protected abstract boolean connectToHal();
  
          protected abstract void dump(PrintWriter pw, String prefix);
  
          protected void resendCurrentTemperatures() {
              synchronized (mHalLock) {
                  List<Temperature> temperatures = getCurrentTemperatures(false, 0);
                  final int count = temperatures.size();
                  for (int i = 0; i < count; i++) {
                      mCallback.onValues(temperatures.get(i));
                  }
              }
          }
  
          final class DeathRecipient implements HwBinder.DeathRecipient {
              @Override
              public void serviceDied(long cookie) {
                  if (cookie == THERMAL_HAL_DEATH_COOKIE) {
                      Slog.e(TAG, "Thermal HAL service died cookie: " + cookie);
                      synchronized (mHalLock) {
                          connectToHal();
                          // Post to listeners after reconnect to HAL.
                          resendCurrentTemperatures();
                      }
                  }
              }
          }
      }
  
  
      static class ThermalHal10Wrapper extends ThermalHalWrapper {
          /** Proxy object for the Thermal HAL 1.0 service. */
          @GuardedBy("mHalLock")
          private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
  
          @Override
          protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<Temperature> ret = new ArrayList<>();
                  if (mThermalHal10 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal10.getTemperatures(
                              (ThermalStatus status,
                                      ArrayList<android.hardware.thermal.V1_0.Temperature>
                                              temperatures) -> {
                                  if (ThermalStatusCode.SUCCESS == status.code) {
                                      for (android.hardware.thermal.V1_0.Temperature
                                              temperature : temperatures) {
                                          if (shouldFilter && type != temperature.type) {
                                              continue;
                                          }
                                          // Thermal HAL 1.0 doesn't report current throttling status
                                          ret.add(new Temperature(
                                                  temperature.currentValue, temperature.type,
                                                  temperature.name,
                                                  Temperature.THROTTLING_NONE));
                                      }
                                  } else {
                                      Slog.e(TAG,
                                              "Couldn't get temperatures because of HAL error: "
                                                      + status.debugMessage);
                                  }
  
                              });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<CoolingDevice> ret = new ArrayList<>();
                  if (mThermalHal10 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal10.getCoolingDevices((status, coolingDevices) -> {
                          if (ThermalStatusCode.SUCCESS == status.code) {
                              for (android.hardware.thermal.V1_0.CoolingDevice
                                      coolingDevice : coolingDevices) {
                                  if (shouldFilter && type != coolingDevice.type) {
                                      continue;
                                  }
                                  ret.add(new CoolingDevice(
                                          (long) coolingDevice.currentValue,
                                          coolingDevice.type,
                                          coolingDevice.name));
                              }
                          } else {
                              Slog.e(TAG,
                                      "Couldn't get cooling device because of HAL error: "
                                              + status.debugMessage);
                          }
  
                      });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected boolean connectToHal() {
              synchronized (mHalLock) {
                  try {
                      mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService();
                      mThermalHal10.linkToDeath(new DeathRecipient(),
                              THERMAL_HAL_DEATH_COOKIE);
                      Slog.i(TAG,
                              "Thermal HAL 1.0 service connected, no thermal call back will be "
                                      + "called due to legacy API.");
                  } catch (NoSuchElementException | RemoteException e) {
                      Slog.e(TAG,
                              "Thermal HAL 1.0 service not connected.");
                      mThermalHal10 = null;
                  }
                  return (mThermalHal10 != null);
              }
          }
  
          @Override
          protected void dump(PrintWriter pw, String prefix) {
              synchronized (mHalLock) {
                  pw.print(prefix);
                  pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes"
                          : "no"));
              }
          }
      }
  
      static class ThermalHal11Wrapper extends ThermalHalWrapper {
          /** Proxy object for the Thermal HAL 1.1 service. */
          @GuardedBy("mHalLock")
          private android.hardware.thermal.V1_1.IThermal mThermalHal11 = null;
  
          /** HWbinder callback for Thermal HAL 1.1. */
          private final IThermalCallback.Stub mThermalCallback11 =
                  new IThermalCallback.Stub() {
                      @Override
                      public void notifyThrottling(boolean isThrottling,
                              android.hardware.thermal.V1_0.Temperature temperature) {
                          Temperature thermalSvcTemp = new Temperature(
                                  temperature.currentValue, temperature.type, temperature.name,
                                  isThrottling ? ThrottlingSeverity.SEVERE
                                          : ThrottlingSeverity.NONE);
                          final long token = Binder.clearCallingIdentity();
                          try {
                              mCallback.onValues(thermalSvcTemp);
                          } finally {
                              Binder.restoreCallingIdentity(token);
                          }
                      }
                  };
  
          @Override
          protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<Temperature> ret = new ArrayList<>();
                  if (mThermalHal11 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal11.getTemperatures(
                              (ThermalStatus status,
                                      ArrayList<android.hardware.thermal.V1_0.Temperature>
                                              temperatures) -> {
                                  if (ThermalStatusCode.SUCCESS == status.code) {
                                      for (android.hardware.thermal.V1_0.Temperature
                                              temperature : temperatures) {
                                          if (shouldFilter && type != temperature.type) {
                                              continue;
                                          }
                                          // Thermal HAL 1.1 doesn't report current throttling status
                                          ret.add(new Temperature(
                                                  temperature.currentValue, temperature.type,
                                                  temperature.name,
                                                  Temperature.THROTTLING_NONE));
                                      }
                                  } else {
                                      Slog.e(TAG,
                                              "Couldn't get temperatures because of HAL error: "
                                                      + status.debugMessage);
                                  }
  
                              });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<CoolingDevice> ret = new ArrayList<>();
                  if (mThermalHal11 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal11.getCoolingDevices((status, coolingDevices) -> {
                          if (ThermalStatusCode.SUCCESS == status.code) {
                              for (android.hardware.thermal.V1_0.CoolingDevice
                                      coolingDevice : coolingDevices) {
                                  if (shouldFilter && type != coolingDevice.type) {
                                      continue;
                                  }
                                  ret.add(new CoolingDevice(
                                          (long) coolingDevice.currentValue,
                                          coolingDevice.type,
                                          coolingDevice.name));
                              }
                          } else {
                              Slog.e(TAG,
                                      "Couldn't get cooling device because of HAL error: "
                                              + status.debugMessage);
                          }
  
                      });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected boolean connectToHal() {
              synchronized (mHalLock) {
                  try {
                      mThermalHal11 = android.hardware.thermal.V1_1.IThermal.getService();
                      mThermalHal11.linkToDeath(new DeathRecipient(),
                              THERMAL_HAL_DEATH_COOKIE);
                      mThermalHal11.registerThermalCallback(mThermalCallback11);
                      Slog.i(TAG, "Thermal HAL 1.1 service connected, limited thermal functions "
                              + "due to legacy API.");
                  } catch (NoSuchElementException | RemoteException e) {
                      Slog.e(TAG, "Thermal HAL 1.1 service not connected.");
                      mThermalHal11 = null;
                  }
                  return (mThermalHal11 != null);
              }
          }
  
          @Override
          protected void dump(PrintWriter pw, String prefix) {
              synchronized (mHalLock) {
                  pw.print(prefix);
                  pw.println("ThermalHAL 1.1 connected: " + (mThermalHal11 != null ? "yes"
                          : "no"));
              }
          }
      }
  
      static class ThermalHal20Wrapper extends ThermalHalWrapper {
          /** Proxy object for the Thermal HAL 2.0 service. */
          @GuardedBy("mHalLock")
          private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
  
          /** HWbinder callback for Thermal HAL 2.0. */
          private final IThermalChangedCallback.Stub mThermalCallback20 =
                  new IThermalChangedCallback.Stub() {
                      @Override
                      public void notifyThrottling(
                              android.hardware.thermal.V2_0.Temperature temperature) {
                          Temperature thermalSvcTemp = new Temperature(
                                  temperature.value, temperature.type, temperature.name,
                                  temperature.throttlingStatus);
                          final long token = Binder.clearCallingIdentity();
                          try {
                              mCallback.onValues(thermalSvcTemp);
                          } finally {
                              Binder.restoreCallingIdentity(token);
                          }
                      }
                  };
  
          @Override
          protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<Temperature> ret = new ArrayList<>();
                  if (mThermalHal20 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal20.getCurrentTemperatures(shouldFilter, type,
                              (status, temperatures) -> {
                                  if (ThermalStatusCode.SUCCESS == status.code) {
                                      for (android.hardware.thermal.V2_0.Temperature
                                              temperature : temperatures) {
                                          ret.add(new Temperature(
                                                  temperature.value, temperature.type,
                                                  temperature.name,
                                                  temperature.throttlingStatus));
                                      }
                                  } else {
                                      Slog.e(TAG,
                                              "Couldn't get temperatures because of HAL error: "
                                                      + status.debugMessage);
                                  }
  
                              });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
                  int type) {
              synchronized (mHalLock) {
                  List<CoolingDevice> ret = new ArrayList<>();
                  if (mThermalHal20 == null) {
                      return ret;
                  }
                  try {
                      mThermalHal20.getCurrentCoolingDevices(shouldFilter, type,
                              (status, coolingDevices) -> {
                                  if (ThermalStatusCode.SUCCESS == status.code) {
                                      for (android.hardware.thermal.V2_0.CoolingDevice
                                              coolingDevice : coolingDevices) {
                                          ret.add(new CoolingDevice(
                                                  coolingDevice.value, coolingDevice.type,
                                                  coolingDevice.name));
                                      }
                                  } else {
                                      Slog.e(TAG,
                                              "Couldn't get cooling device because of HAL error: "
                                                      + status.debugMessage);
                                  }
  
                              });
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting...", e);
                      connectToHal();
                  }
                  return ret;
              }
          }
  
          @Override
          protected boolean connectToHal() {
              synchronized (mHalLock) {
                  try {
                      mThermalHal20 = android.hardware.thermal.V2_0.IThermal.getService();
                      mThermalHal20.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
                      mThermalHal20.registerThermalChangedCallback(mThermalCallback20, false,
                              0 /* not used */);
                      Slog.i(TAG, "Thermal HAL 2.0 service connected.");
                  } catch (NoSuchElementException | RemoteException e) {
                      Slog.e(TAG, "Thermal HAL 2.0 service not connected.");
                      mThermalHal20 = null;
                  }
                  return (mThermalHal20 != null);
              }
          }
  
          @Override
          protected void dump(PrintWriter pw, String prefix) {
              synchronized (mHalLock) {
                  pw.print(prefix);
                  pw.println("ThermalHAL 2.0 connected: " + (mThermalHal20 != null ? "yes"
                          : "no"));
              }
          }
      }
  
  }
  