/* * 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 mWifiLocks = new ArrayList<>(); // map UIDs to their corresponding records (for low-latency locks) private final SparseArray 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) { if (!isValidLockMode(lockMode)) { throw new IllegalArgumentException("lockMode =" + lockMode); } // 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); } } } private 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 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 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; } } }