content: /*
   * Copyright (C) 2016 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.wifi;
  
  import android.app.ActivityManager;
  import android.content.Context;
  import android.net.wifi.WifiManager;
  import android.os.Binder;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.RemoteException;
  import android.os.WorkSource;
  import android.os.WorkSource.WorkChain;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.StatsLog;
  
  import com.android.internal.app.IBatteryStats;
  
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * WifiLockManager maintains the list of wake locks held by different applications.
   */
  public class WifiLockManager {
      private static final String TAG = "WifiLockManager";
  
      private static final int LOW_LATENCY_SUPPORT_UNDEFINED = -1;
      private static final int LOW_LATENCY_NOT_SUPPORTED     =  0;
      private static final int LOW_LATENCY_SUPPORTED         =  1;
  
      private static final int IGNORE_SCREEN_STATE_MASK = 0x01;
      private static final int IGNORE_WIFI_STATE_MASK   = 0x02;
  
      private int mLatencyModeSupport = LOW_LATENCY_SUPPORT_UNDEFINED;
  
      private boolean mVerboseLoggingEnabled = false;
  
      private final Clock mClock;
      private final Context mContext;
      private final IBatteryStats mBatteryStats;
      private final FrameworkFacade mFrameworkFacade;
      private final ClientModeImpl mClientModeImpl;
      private final ActivityManager mActivityManager;
      private final Handler mHandler;
      private final WifiMetrics mWifiMetrics;
      private final WifiNative mWifiNative;
  
      private final List<WifiLock> mWifiLocks = new ArrayList<>();
      // map UIDs to their corresponding records (for low-latency locks)
      private final SparseArray<UidRec> mLowLatencyUidWatchList = new SparseArray<>();
      private int mCurrentOpMode;
      private boolean mScreenOn = false;
      private boolean mWifiConnected = false;
  
      // For shell command support
      private boolean mForceHiPerfMode = false;
      private boolean mForceLowLatencyMode = false;
  
      // some wifi lock statistics
      private int mFullHighPerfLocksAcquired;
      private int mFullHighPerfLocksReleased;
      private int mFullLowLatencyLocksAcquired;
      private int mFullLowLatencyLocksReleased;
      private long mCurrentSessionStartTimeMs;
  
      WifiLockManager(Context context, IBatteryStats batteryStats,
              ClientModeImpl clientModeImpl, FrameworkFacade frameworkFacade, Handler handler,
              WifiNative wifiNative, Clock clock, WifiMetrics wifiMetrics) {
          mContext = context;
          mBatteryStats = batteryStats;
          mClientModeImpl = clientModeImpl;
          mFrameworkFacade = frameworkFacade;
          mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
          mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
          mWifiNative = wifiNative;
          mHandler = handler;
          mClock = clock;
          mWifiMetrics = wifiMetrics;
  
          // Register for UID fg/bg transitions
          registerUidImportanceTransitions();
      }
  
      // Check for conditions to activate high-perf lock
      private boolean canActivateHighPerfLock(int ignoreMask) {
          boolean check = true;
  
          // Only condition is when Wifi is connected
          if ((ignoreMask & IGNORE_WIFI_STATE_MASK) == 0) {
              check = check && mWifiConnected;
          }
  
          return check;
      }
  
      private boolean canActivateHighPerfLock() {
          return canActivateHighPerfLock(0);
      }
  
      // Check for conditions to activate low-latency lock
      private boolean canActivateLowLatencyLock(int ignoreMask, UidRec uidRec) {
          boolean check = true;
  
          if ((ignoreMask & IGNORE_WIFI_STATE_MASK) == 0) {
              check = check && mWifiConnected;
          }
          if ((ignoreMask & IGNORE_SCREEN_STATE_MASK) == 0) {
              check = check && mScreenOn;
          }
          if (uidRec != null) {
              check = check && uidRec.mIsFg;
          }
  
          return check;
      }
  
      private boolean canActivateLowLatencyLock(int ignoreMask) {
          return canActivateLowLatencyLock(ignoreMask, null);
      }
  
      private boolean canActivateLowLatencyLock() {
          return canActivateLowLatencyLock(0, null);
      }
  
      // Detect UIDs going foreground/background
      private void registerUidImportanceTransitions() {
          mActivityManager.addOnUidImportanceListener(new ActivityManager.OnUidImportanceListener() {
              @Override
              public void onUidImportance(final int uid, final int importance) {
                  mHandler.post(() -> {
                      UidRec uidRec = mLowLatencyUidWatchList.get(uid);
                      if (uidRec == null) {
                          // Not a uid in the watch list
                          return;
                      }
  
                      boolean newModeIsFg = (importance
                              == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
                      if (uidRec.mIsFg == newModeIsFg) {
                          return; // already at correct state
                      }
  
                      uidRec.mIsFg = newModeIsFg;
                      updateOpMode();
  
                      // If conditions for lock activation are met,
                      // then UID either share the blame, or removed from sharing
                      // whether to start or stop the blame based on UID fg/bg state
                      if (canActivateLowLatencyLock()) {
                          setBlameLowLatencyUid(uid, uidRec.mIsFg);
                      }
                  });
              }
          }, ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
      }
  
      /**
       * Method allowing a calling app to acquire a Wifi WakeLock in the supplied mode.
       *
       * This method checks that the lock mode is a valid WifiLock mode.
       * @param lockMode int representation of the Wifi WakeLock type.
       * @param tag String passed to WifiManager.WifiLock
       * @param binder IBinder for the calling app
       * @param ws WorkSource of the calling app
       *
       * @return true if the lock was successfully acquired, false if the lockMode was invalid.
       */
      public boolean acquireWifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
          // Make a copy of the WorkSource before adding it to the WakeLock
          // This is to make sure worksource value can not be changed by caller
          // after function returns.
          WorkSource newWorkSource = new WorkSource(ws);
  
          return addLock(new WifiLock(lockMode, tag, binder, newWorkSource));
      }
  
      /**
       * Method used by applications to release a WiFi Wake lock.
       *
       * @param binder IBinder for the calling app.
       * @return true if the lock was released, false if the caller did not hold any locks
       */
      public boolean releaseWifiLock(IBinder binder) {
          return releaseLock(binder);
      }
  
      /**
       * Method used to get the strongest lock type currently held by the WifiLockManager.
       *
       * If no locks are held, WifiManager.WIFI_MODE_NO_LOCKS_HELD is returned.
       *
       * @return int representing the currently held (highest power consumption) lock.
       */
      public synchronized int getStrongestLockMode() {
          // If Wifi Client is not connected, then all locks are not effective
          if (!mWifiConnected) {
              return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
          }
  
          // Check if mode is forced to hi-perf
          if (mForceHiPerfMode) {
              return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
          }
  
          // Check if mode is forced to low-latency
          if (mForceLowLatencyMode) {
              return WifiManager.WIFI_MODE_FULL_LOW_LATENCY;
          }
  
          if (mScreenOn && countFgLowLatencyUids() > 0) {
              return WifiManager.WIFI_MODE_FULL_LOW_LATENCY;
          }
  
          if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
              return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
          }
  
          return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
      }
  
      /**
       * Method to create a WorkSource containing all active WifiLock WorkSources.
       */
      public synchronized WorkSource createMergedWorkSource() {
          WorkSource mergedWS = new WorkSource();
          for (WifiLock lock : mWifiLocks) {
              mergedWS.add(lock.getWorkSource());
          }
          return mergedWS;
      }
  
      /**
       * Method used to update WifiLocks with a new WorkSouce.
       *
       * @param binder IBinder for the calling application.
       * @param ws WorkSource to add to the existing WifiLock(s).
       */
      public synchronized void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
  
          // Now check if there is an active lock
          WifiLock wl = findLockByBinder(binder);
          if (wl == null) {
              throw new IllegalArgumentException("Wifi lock not active");
          }
  
          // Make a copy of the WorkSource before adding it to the WakeLock
          // This is to make sure worksource value can not be changed by caller
          // after function returns.
          WorkSource newWorkSource = new WorkSource(ws);
  
          if (mVerboseLoggingEnabled) {
              Slog.d(TAG, "updateWifiLockWakeSource: " + wl + ", newWorkSource=" + newWorkSource);
          }
  
          // Note:
          // Log the acquire before the release to avoid "holes" in the collected data due to
          // an acquire event immediately after a release in the case where newWorkSource and
          // wl.mWorkSource share one or more attribution UIDs. Both batteryStats and statsd
          // can correctly match "nested" acquire / release pairs.
          switch(wl.mMode) {
              case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                  // Shift blame to new worksource if needed
                  if (canActivateHighPerfLock()) {
                      setBlameHiPerfWs(newWorkSource, true);
                      setBlameHiPerfWs(wl.mWorkSource, false);
                  }
                  break;
              case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
                  addWsToLlWatchList(newWorkSource);
                  removeWsFromLlWatchList(wl.mWorkSource);
                  updateOpMode();
                  break;
              default:
                  // Do nothing
                  break;
          }
  
          wl.mWorkSource = newWorkSource;
      }
  
      /**
       * Method Used for shell command support
       *
       * @param isEnabled True to force hi-perf mode, false to leave it up to acquired wifiLocks.
       * @return True for success, false for failure (failure turns forcing mode off)
       */
      public boolean forceHiPerfMode(boolean isEnabled) {
          mForceHiPerfMode = isEnabled;
          mForceLowLatencyMode = false;
          if (!updateOpMode()) {
              Slog.e(TAG, "Failed to force hi-perf mode, returning to normal mode");
              mForceHiPerfMode = false;
              return false;
          }
          return true;
      }
  
      /**
       * Method Used for shell command support
       *
       * @param isEnabled True to force low-latency mode, false to leave it up to acquired wifiLocks.
       * @return True for success, false for failure (failure turns forcing mode off)
       */
      public boolean forceLowLatencyMode(boolean isEnabled) {
          mForceLowLatencyMode = isEnabled;
          mForceHiPerfMode = false;
          if (!updateOpMode()) {
              Slog.e(TAG, "Failed to force low-latency mode, returning to normal mode");
              mForceLowLatencyMode = false;
              return false;
          }
          return true;
      }
  
      /**
       * Handler for screen state (on/off) changes
       */
      public void handleScreenStateChanged(boolean screenOn) {
          if (mVerboseLoggingEnabled) {
              Slog.d(TAG, "handleScreenStateChanged: screenOn = " + screenOn);
          }
  
          mScreenOn = screenOn;
  
          if (canActivateLowLatencyLock(IGNORE_SCREEN_STATE_MASK)) {
              // Update the running mode
              updateOpMode();
              // Adjust blaming for UIDs in foreground
              setBlameLowLatencyWatchList(screenOn);
          }
      }
  
      /**
       * Handler for Wifi Client mode state changes
       */
      public void updateWifiClientConnected(boolean isConnected) {
          if (mWifiConnected == isConnected) {
              // No need to take action
              return;
          }
          mWifiConnected = isConnected;
  
          // Adjust blaming for UIDs in foreground carrying low latency locks
          if (canActivateLowLatencyLock(IGNORE_WIFI_STATE_MASK)) {
              setBlameLowLatencyWatchList(mWifiConnected);
          }
  
          // Adjust blaming for UIDs carrying high perf locks
          // Note that blaming is adjusted only if needed,
          // since calling this API is reference counted
          if (canActivateHighPerfLock(IGNORE_WIFI_STATE_MASK)) {
              setBlameHiPerfLocks(mWifiConnected);
          }
  
          updateOpMode();
      }
  
      private void setBlameHiPerfLocks(boolean shouldBlame) {
          for (WifiLock lock : mWifiLocks) {
              if (lock.mMode == WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
                  setBlameHiPerfWs(lock.getWorkSource(), shouldBlame);
              }
          }
      }
  
      /**
       * Validate that the lock mode is valid - i.e. one of the supported enumerations.
       *
       * @param lockMode The lock mode to verify.
       * @return true for valid lock modes, false otherwise.
       */
      public static boolean isValidLockMode(int lockMode) {
          if (lockMode != WifiManager.WIFI_MODE_FULL
                  && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY
                  && lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF
                  && lockMode != WifiManager.WIFI_MODE_FULL_LOW_LATENCY) {
              return false;
          }
          return true;
      }
  
      private void addUidToLlWatchList(int uid) {
          UidRec uidRec = mLowLatencyUidWatchList.get(uid);
          if (uidRec != null) {
              uidRec.mLockCount++;
          } else {
              uidRec = new UidRec(uid);
              uidRec.mLockCount = 1;
              mLowLatencyUidWatchList.put(uid, uidRec);
  
              // Now check if the uid is running in foreground
              if (mFrameworkFacade.isAppForeground(uid)) {
                  uidRec.mIsFg = true;
              }
  
              if (canActivateLowLatencyLock(0, uidRec)) {
                  // Share the blame for this uid
                  setBlameLowLatencyUid(uid, true);
              }
          }
      }
  
      private void removeUidFromLlWatchList(int uid) {
          UidRec uidRec = mLowLatencyUidWatchList.get(uid);
          if (uidRec == null) {
              Slog.e(TAG, "Failed to find uid in low-latency watch list");
              return;
          }
  
          if (uidRec.mLockCount > 0) {
              uidRec.mLockCount--;
          } else {
              Slog.e(TAG, "Error, uid record conatains no locks");
          }
          if (uidRec.mLockCount == 0) {
              mLowLatencyUidWatchList.remove(uid);
  
              // Remove blame for this UID if it was alerady set
              // Note that blame needs to be stopped only if it was started before
              // to avoid calling the API unnecessarily, since it is reference counted
              if (canActivateLowLatencyLock(0, uidRec)) {
                  setBlameLowLatencyUid(uid, false);
              }
          }
      }
  
      private void addWsToLlWatchList(WorkSource ws) {
          int wsSize = ws.size();
          for (int i = 0; i < wsSize; i++) {
              final int uid = ws.get(i);
              addUidToLlWatchList(uid);
          }
  
          final List<WorkChain> workChains = ws.getWorkChains();
          if (workChains != null) {
              for (int i = 0; i < workChains.size(); ++i) {
                  final WorkChain workChain = workChains.get(i);
                  final int uid = workChain.getAttributionUid();
                  addUidToLlWatchList(uid);
              }
          }
      }
  
      private void removeWsFromLlWatchList(WorkSource ws) {
          int wsSize = ws.size();
          for (int i = 0; i < wsSize; i++) {
              final int uid = ws.get(i);
              removeUidFromLlWatchList(uid);
          }
  
          final List<WorkChain> workChains = ws.getWorkChains();
          if (workChains != null) {
              for (int i = 0; i < workChains.size(); ++i) {
                  final WorkChain workChain = workChains.get(i);
                  final int uid = workChain.getAttributionUid();
                  removeUidFromLlWatchList(uid);
              }
          }
      }
  
      private synchronized boolean addLock(WifiLock lock) {
          if (mVerboseLoggingEnabled) {
              Slog.d(TAG, "addLock: " + lock);
          }
  
          if (findLockByBinder(lock.getBinder()) != null) {
              if (mVerboseLoggingEnabled) {
                  Slog.d(TAG, "attempted to add a lock when already holding one");
              }
              return false;
          }
  
          mWifiLocks.add(lock);
  
          switch(lock.mMode) {
              case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                  ++mFullHighPerfLocksAcquired;
                  // Start blaming this worksource if conditions are met
                  if (canActivateHighPerfLock()) {
                      setBlameHiPerfWs(lock.mWorkSource, true);
                  }
                  break;
              case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
                  addWsToLlWatchList(lock.getWorkSource());
                  ++mFullLowLatencyLocksAcquired;
                  break;
              default:
                  // Do nothing
                  break;
          }
  
          // Recalculate the operating mode
          updateOpMode();
  
          return true;
      }
  
      private synchronized WifiLock removeLock(IBinder binder) {
          WifiLock lock = findLockByBinder(binder);
          if (lock != null) {
              mWifiLocks.remove(lock);
              lock.unlinkDeathRecipient();
          }
          return lock;
      }
  
      private synchronized boolean releaseLock(IBinder binder) {
          WifiLock wifiLock = removeLock(binder);
          if (wifiLock == null) {
              // attempting to release a lock that does not exist.
              return false;
          }
  
          if (mVerboseLoggingEnabled) {
              Slog.d(TAG, "releaseLock: " + wifiLock);
          }
  
          switch(wifiLock.mMode) {
              case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                  ++mFullHighPerfLocksReleased;
                  mWifiMetrics.addWifiLockAcqSession(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                          mClock.getElapsedSinceBootMillis() - wifiLock.getAcqTimestamp());
                  // Stop blaming only if blaming was set before (conditions are met).
                  // This is to avoid calling the api unncessarily, since this API is
                  // reference counted in batteryStats and statsd
                  if (canActivateHighPerfLock()) {
                      setBlameHiPerfWs(wifiLock.mWorkSource, false);
                  }
                  break;
              case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
                  removeWsFromLlWatchList(wifiLock.getWorkSource());
                  ++mFullLowLatencyLocksReleased;
                  mWifiMetrics.addWifiLockAcqSession(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                          mClock.getElapsedSinceBootMillis() - wifiLock.getAcqTimestamp());
                  break;
              default:
                  // Do nothing
                  break;
          }
  
          // Recalculate the operating mode
          updateOpMode();
  
          return true;
      }
  
      private synchronized boolean updateOpMode() {
          final int newLockMode = getStrongestLockMode();
  
          if (newLockMode == mCurrentOpMode) {
              // No action is needed
              return true;
          }
  
          if (mVerboseLoggingEnabled) {
              Slog.d(TAG, "Current opMode: " + mCurrentOpMode + " New LockMode: " + newLockMode);
          }
  
          // Otherwise, we need to change current mode, first reset it to normal
          switch (mCurrentOpMode) {
              case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                  if (!mClientModeImpl.setPowerSave(true)) {
                      Slog.e(TAG, "Failed to reset the OpMode from hi-perf to Normal");
                      return false;
                  }
                  mWifiMetrics.addWifiLockActiveSession(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                          mClock.getElapsedSinceBootMillis() - mCurrentSessionStartTimeMs);
                  break;
  
              case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
                  if (!setLowLatencyMode(false)) {
                      Slog.e(TAG, "Failed to reset the OpMode from low-latency to Normal");
                      return false;
                  }
                  mWifiMetrics.addWifiLockActiveSession(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                          mClock.getElapsedSinceBootMillis() - mCurrentSessionStartTimeMs);
                  break;
  
              case WifiManager.WIFI_MODE_NO_LOCKS_HELD:
              default:
                  // No action
                  break;
          }
  
          // Set the current mode, before we attempt to set the new mode
          mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
  
          // Now switch to the new opMode
          switch (newLockMode) {
              case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
                  if (!mClientModeImpl.setPowerSave(false)) {
                      Slog.e(TAG, "Failed to set the OpMode to hi-perf");
                      return false;
                  }
                  mCurrentSessionStartTimeMs = mClock.getElapsedSinceBootMillis();
                  break;
  
              case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
                  if (!setLowLatencyMode(true)) {
                      Slog.e(TAG, "Failed to set the OpMode to low-latency");
                      return false;
                  }
                  mCurrentSessionStartTimeMs = mClock.getElapsedSinceBootMillis();
                  break;
  
              case WifiManager.WIFI_MODE_NO_LOCKS_HELD:
                  // No action
                  break;
  
              default:
                  // Invalid mode, don't change currentOpMode , and exit with error
                  Slog.e(TAG, "Invalid new opMode: " + newLockMode);
                  return false;
          }
  
          // Now set the mode to the new value
          mCurrentOpMode = newLockMode;
          return true;
      }
  
      private int getLowLatencyModeSupport() {
          if (mLatencyModeSupport == LOW_LATENCY_SUPPORT_UNDEFINED) {
              String ifaceName = mWifiNative.getClientInterfaceName();
              if (ifaceName == null) {
                  return LOW_LATENCY_SUPPORT_UNDEFINED;
              }
  
              long supportedFeatures = mWifiNative.getSupportedFeatureSet(ifaceName);
              if (supportedFeatures != 0) {
                  if ((supportedFeatures & WifiManager.WIFI_FEATURE_LOW_LATENCY) != 0) {
                      mLatencyModeSupport = LOW_LATENCY_SUPPORTED;
                  } else {
                      mLatencyModeSupport = LOW_LATENCY_NOT_SUPPORTED;
                  }
              }
          }
  
          return mLatencyModeSupport;
      }
  
      private boolean setLowLatencyMode(boolean enabled) {
          int lowLatencySupport = getLowLatencyModeSupport();
  
          if (lowLatencySupport == LOW_LATENCY_SUPPORT_UNDEFINED) {
              // Support undefined, no action is taken
              return false;
          }
  
          if (lowLatencySupport == LOW_LATENCY_SUPPORTED) {
              if (!mClientModeImpl.setLowLatencyMode(enabled)) {
                  Slog.e(TAG, "Failed to set low latency mode");
                  return false;
              }
  
              if (!mClientModeImpl.setPowerSave(!enabled)) {
                  Slog.e(TAG, "Failed to set power save mode");
                  // Revert the low latency mode
                  mClientModeImpl.setLowLatencyMode(!enabled);
                  return false;
              }
          } else if (lowLatencySupport == LOW_LATENCY_NOT_SUPPORTED) {
              // Only set power save mode
              if (!mClientModeImpl.setPowerSave(!enabled)) {
                  Slog.e(TAG, "Failed to set power save mode");
                  return false;
              }
          }
  
          return true;
      }
  
      private synchronized WifiLock findLockByBinder(IBinder binder) {
          for (WifiLock lock : mWifiLocks) {
              if (lock.getBinder() == binder) {
                  return lock;
              }
          }
          return null;
      }
  
      private int countFgLowLatencyUids() {
          int uidCount = 0;
          int listSize = mLowLatencyUidWatchList.size();
          for (int idx = 0; idx < listSize; idx++) {
              UidRec uidRec = mLowLatencyUidWatchList.valueAt(idx);
              if (uidRec.mIsFg) {
                  uidCount++;
              }
          }
          return uidCount;
      }
  
      private void setBlameHiPerfWs(WorkSource ws, boolean shouldBlame) {
          long ident = Binder.clearCallingIdentity();
          try {
              if (shouldBlame) {
                  mBatteryStats.noteFullWifiLockAcquiredFromSource(ws);
                  StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, ws,
                          StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON,
                          WifiManager.WIFI_MODE_FULL_HIGH_PERF);
              } else {
                  mBatteryStats.noteFullWifiLockReleasedFromSource(ws);
                  StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, ws,
                          StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF,
                          WifiManager.WIFI_MODE_FULL_HIGH_PERF);
              }
          } catch (RemoteException e) {
              // nop
          } finally {
              Binder.restoreCallingIdentity(ident);
          }
      }
  
      private void setBlameLowLatencyUid(int uid, boolean shouldBlame) {
          long ident = Binder.clearCallingIdentity();
          try {
              if (shouldBlame) {
                  mBatteryStats.noteFullWifiLockAcquired(uid);
                  StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, null,
                          StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON,
                          WifiManager.WIFI_MODE_FULL_LOW_LATENCY);
              } else {
                  mBatteryStats.noteFullWifiLockReleased(uid);
                  StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, null,
                          StatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF,
                          WifiManager.WIFI_MODE_FULL_LOW_LATENCY);
              }
          } catch (RemoteException e) {
              // nop
          } finally {
              Binder.restoreCallingIdentity(ident);
          }
      }
  
      private void setBlameLowLatencyWatchList(boolean shouldBlame) {
          for (int idx = 0; idx < mLowLatencyUidWatchList.size(); idx++) {
              UidRec uidRec = mLowLatencyUidWatchList.valueAt(idx);
              // Affect the blame for only UIDs running in foreground
              // UIDs running in the background are already not blamed,
              // and they should remain in that state.
              if (uidRec.mIsFg) {
                  setBlameLowLatencyUid(uidRec.mUid, shouldBlame);
              }
          }
      }
  
      protected void dump(PrintWriter pw) {
          pw.println("Locks acquired: "
                  + mFullHighPerfLocksAcquired + " full high perf, "
                  + mFullLowLatencyLocksAcquired + " full low latency");
          pw.println("Locks released: "
                  + mFullHighPerfLocksReleased + " full high perf, "
                  + mFullLowLatencyLocksReleased + " full low latency");
  
          pw.println();
          pw.println("Locks held:");
          for (WifiLock lock : mWifiLocks) {
              pw.print("    ");
              pw.println(lock);
          }
      }
  
      protected void enableVerboseLogging(int verbose) {
          if (verbose > 0) {
              mVerboseLoggingEnabled = true;
          } else {
              mVerboseLoggingEnabled = false;
          }
      }
  
      private class WifiLock implements IBinder.DeathRecipient {
          String mTag;
          int mUid;
          IBinder mBinder;
          int mMode;
          WorkSource mWorkSource;
          long mAcqTimestamp;
  
          WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
              mTag = tag;
              mBinder = binder;
              mUid = Binder.getCallingUid();
              mMode = lockMode;
              mWorkSource = ws;
              mAcqTimestamp = mClock.getElapsedSinceBootMillis();
              try {
                  mBinder.linkToDeath(this, 0);
              } catch (RemoteException e) {
                  binderDied();
              }
          }
  
          protected WorkSource getWorkSource() {
              return mWorkSource;
          }
  
          protected int getUid() {
              return mUid;
          }
  
          protected IBinder getBinder() {
              return mBinder;
          }
  
          protected long getAcqTimestamp() {
              return mAcqTimestamp;
          }
  
          public void binderDied() {
              releaseLock(mBinder);
          }
  
          public void unlinkDeathRecipient() {
              mBinder.unlinkToDeath(this, 0);
          }
  
          public String toString() {
              return "WifiLock{" + this.mTag + " type=" + this.mMode + " uid=" + mUid
                      + " workSource=" + mWorkSource + "}";
          }
      }
  
      private class UidRec {
          final int mUid;
          // Count of locks owned or co-owned by this UID
          int mLockCount;
          // Is this UID running in foreground
          boolean mIsFg;
  
          UidRec(int uid) {
              mUid = uid;
          }
      }
  }
  