913 lines
33 KiB
Java
913 lines
33 KiB
Java
|
/*
|
||
|
* 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.annotation.NonNull;
|
||
|
import android.app.ActivityManager;
|
||
|
import android.content.BroadcastReceiver;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.IntentFilter;
|
||
|
import android.net.wifi.WifiManager;
|
||
|
import android.os.BatteryStatsManager;
|
||
|
import android.os.Binder;
|
||
|
import android.os.Handler;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.PowerManager;
|
||
|
import android.os.RemoteException;
|
||
|
import android.os.WorkSource;
|
||
|
import android.os.WorkSource.WorkChain;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.Log;
|
||
|
import android.util.Pair;
|
||
|
import android.util.SparseArray;
|
||
|
|
||
|
import com.android.internal.annotations.VisibleForTesting;
|
||
|
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 ActiveModeWarden mActiveModeWarden;
|
||
|
private final ActivityManager mActivityManager;
|
||
|
private final Handler mHandler;
|
||
|
private final WifiMetrics mWifiMetrics;
|
||
|
|
||
|
private final List<WifiLock> mWifiLocks = new ArrayList<>();
|
||
|
// map UIDs to their corresponding records (for low-latency locks)
|
||
|
private final SparseArray<UidRec> mLowLatencyUidWatchList = new SparseArray<>();
|
||
|
/** the current op mode of the primary ClientModeManager */
|
||
|
private int mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
|
||
|
private boolean mScreenOn = false;
|
||
|
/** whether Wifi is connected on the primary ClientModeManager */
|
||
|
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,
|
||
|
ActiveModeWarden activeModeWarden, FrameworkFacade frameworkFacade,
|
||
|
Handler handler, Clock clock, WifiMetrics wifiMetrics) {
|
||
|
mContext = context;
|
||
|
mBatteryStats = batteryStats;
|
||
|
mActiveModeWarden = activeModeWarden;
|
||
|
mFrameworkFacade = frameworkFacade;
|
||
|
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
|
||
|
mHandler = handler;
|
||
|
mClock = clock;
|
||
|
mWifiMetrics = wifiMetrics;
|
||
|
|
||
|
IntentFilter filter = new IntentFilter();
|
||
|
filter.addAction(Intent.ACTION_SCREEN_ON);
|
||
|
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||
|
context.registerReceiver(
|
||
|
new BroadcastReceiver() {
|
||
|
@Override
|
||
|
public void onReceive(Context context, Intent intent) {
|
||
|
String action = intent.getAction();
|
||
|
if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
|
||
|
handleScreenStateChanged(true);
|
||
|
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
|
||
|
handleScreenStateChanged(false);
|
||
|
}
|
||
|
}
|
||
|
}, filter, null, mHandler);
|
||
|
handleScreenStateChanged(context.getSystemService(PowerManager.class).isInteractive());
|
||
|
|
||
|
// 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.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
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
|
||
|
*/
|
||
|
private 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(
|
||
|
ClientModeManager clientModeManager, boolean isConnected) {
|
||
|
boolean hasAtLeastOneConnection = isConnected
|
||
|
|| mActiveModeWarden.getClientModeManagers().stream().anyMatch(
|
||
|
cmm -> cmm.isConnected());
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "updateWifiClientConnected hasAtLeastOneConnection="
|
||
|
+ hasAtLeastOneConnection);
|
||
|
}
|
||
|
if (mWifiConnected == hasAtLeastOneConnection) {
|
||
|
// No need to take action
|
||
|
return;
|
||
|
}
|
||
|
mWifiConnected = hasAtLeastOneConnection;
|
||
|
|
||
|
// 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 synchronized 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reset the given ClientModeManager's power save/low latency mode to the default.
|
||
|
* The method calls needed to reset is the reverse of the method calls used to set.
|
||
|
* @return true if the operation succeeded, false otherwise
|
||
|
*/
|
||
|
private boolean resetCurrentMode(@NonNull ClientModeManager clientModeManager) {
|
||
|
switch (mCurrentOpMode) {
|
||
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
||
|
if (!clientModeManager.setPowerSave(ClientMode.POWER_SAVE_CLIENT_WIFI_LOCK,
|
||
|
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(clientModeManager, 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;
|
||
|
}
|
||
|
|
||
|
// reset the current mode
|
||
|
mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the new lock mode on the given ClientModeManager
|
||
|
* @return true if the operation succeeded, false otherwise
|
||
|
*/
|
||
|
private boolean setNewMode(@NonNull ClientModeManager clientModeManager, int newLockMode) {
|
||
|
switch (newLockMode) {
|
||
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
||
|
if (!clientModeManager.setPowerSave(ClientMode.POWER_SAVE_CLIENT_WIFI_LOCK,
|
||
|
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(clientModeManager, 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 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);
|
||
|
}
|
||
|
|
||
|
ClientModeManager primaryManager = mActiveModeWarden.getPrimaryClientModeManager();
|
||
|
|
||
|
// Otherwise, we need to change current mode, first reset it to normal
|
||
|
if (!resetCurrentMode(primaryManager)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Now switch to the new opMode
|
||
|
return setNewMode(primaryManager, newLockMode);
|
||
|
}
|
||
|
|
||
|
/** Returns the cached low latency mode support value, or tries to fetch it if not yet known. */
|
||
|
private int getLowLatencyModeSupport() {
|
||
|
if (mLatencyModeSupport != LOW_LATENCY_SUPPORT_UNDEFINED) {
|
||
|
return mLatencyModeSupport;
|
||
|
}
|
||
|
|
||
|
long supportedFeatures =
|
||
|
mActiveModeWarden.getPrimaryClientModeManager().getSupportedFeatures();
|
||
|
if (supportedFeatures == 0L) {
|
||
|
return LOW_LATENCY_SUPPORT_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
if ((supportedFeatures & WifiManager.WIFI_FEATURE_LOW_LATENCY) != 0) {
|
||
|
mLatencyModeSupport = LOW_LATENCY_SUPPORTED;
|
||
|
} else {
|
||
|
mLatencyModeSupport = LOW_LATENCY_NOT_SUPPORTED;
|
||
|
}
|
||
|
return mLatencyModeSupport;
|
||
|
}
|
||
|
|
||
|
private boolean setLowLatencyMode(ClientModeManager clientModeManager, 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 (!clientModeManager.setLowLatencyMode(enabled)) {
|
||
|
Log.e(TAG, "Failed to set low latency mode");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!clientModeManager.setPowerSave(ClientMode.POWER_SAVE_CLIENT_WIFI_LOCK,
|
||
|
!enabled)) {
|
||
|
Log.e(TAG, "Failed to set power save mode");
|
||
|
// Revert the low latency mode
|
||
|
clientModeManager.setLowLatencyMode(!enabled);
|
||
|
return false;
|
||
|
}
|
||
|
} else if (lowLatencySupport == LOW_LATENCY_NOT_SUPPORTED) {
|
||
|
// Only set power save mode
|
||
|
if (!clientModeManager.setPowerSave(ClientMode.POWER_SAVE_CLIENT_WIFI_LOCK,
|
||
|
!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 synchronized 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(boolean verboseEnabled) {
|
||
|
mVerboseLoggingEnabled = verboseEnabled;
|
||
|
}
|
||
|
|
||
|
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() {
|
||
|
mHandler.post(() -> 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;
|
||
|
}
|
||
|
}
|
||
|
}
|