/*
 * 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.BatteryStatsManager;
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.Log;
import android.util.Pair;
import android.util.SparseArray;

import com.android.server.wifi.proto.WifiStatsLog;
import com.android.server.wifi.util.WorkSourceUtil;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * 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 BatteryStatsManager 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, BatteryStatsManager 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) {
            Log.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()) {
            Log.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()) {
            Log.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) {
            Log.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(mContext, 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) {
            Log.e(TAG, "Failed to find uid in low-latency watch list");
            return;
        }

        if (uidRec.mLockCount > 0) {
            uidRec.mLockCount--;
        } else {
            Log.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.getUid(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.getUid(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) {
            Log.d(TAG, "addLock: " + lock);
        }

        if (findLockByBinder(lock.getBinder()) != null) {
            if (mVerboseLoggingEnabled) {
                Log.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) {
            Log.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) {
            Log.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)) {
                    Log.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)) {
                    Log.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)) {
                    Log.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)) {
                    Log.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
                Log.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)) {
                Log.e(TAG, "Failed to set low latency mode");
                return false;
            }

            if (!mClientModeImpl.setPowerSave(!enabled)) {
                Log.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)) {
                Log.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();
        Pair<int[], String[]> uidsAndTags = WorkSourceUtil.getUidsAndTagsForWs(ws);
        try {
            if (shouldBlame) {
                mBatteryStats.reportFullWifiLockAcquiredFromSource(ws);
                WifiStatsLog.write(WifiStatsLog.WIFI_LOCK_STATE_CHANGED,
                        uidsAndTags.first, uidsAndTags.second,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__MODE__WIFI_MODE_FULL_HIGH_PERF);
            } else {
                mBatteryStats.reportFullWifiLockReleasedFromSource(ws);
                WifiStatsLog.write(WifiStatsLog.WIFI_LOCK_STATE_CHANGED,
                        uidsAndTags.first, uidsAndTags.second,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__MODE__WIFI_MODE_FULL_HIGH_PERF);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void setBlameLowLatencyUid(int uid, boolean shouldBlame) {
        long ident = Binder.clearCallingIdentity();
        try {
            if (shouldBlame) {
                mBatteryStats.reportFullWifiLockAcquiredFromSource(new WorkSource(uid));
                WifiStatsLog.write_non_chained(WifiStatsLog.WIFI_LOCK_STATE_CHANGED, uid, null,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__STATE__ON,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__MODE__WIFI_MODE_FULL_LOW_LATENCY);
            } else {
                mBatteryStats.reportFullWifiLockReleasedFromSource(new WorkSource(uid));
                WifiStatsLog.write_non_chained(WifiStatsLog.WIFI_LOCK_STATE_CHANGED, uid, null,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__STATE__OFF,
                        WifiStatsLog.WIFI_LOCK_STATE_CHANGED__MODE__WIFI_MODE_FULL_LOW_LATENCY);
            }
        } 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) {
                Log.e(TAG, "mBinder.linkToDeath failed: " + e.getMessage());
                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() {
            try {
                mBinder.unlinkToDeath(this, 0);
            } catch (NoSuchElementException e) {
                Log.e(TAG, "mBinder.unlinkToDeath failed: " + e.getMessage());
            }
        }

        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;
        }
    }
}
