| /* |
| * 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 static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| 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.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 (action.equals(Intent.ACTION_SCREEN_ON)) { |
| handleScreenStateChanged(true); |
| } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { |
| handleScreenStateChanged(false); |
| } |
| } |
| }, filter, null, mHandler); |
| handleScreenStateChanged(context.getSystemService(PowerManager.class).isInteractive()); |
| |
| // Register for UID fg/bg transitions |
| registerUidImportanceTransitions(); |
| |
| mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback( |
| new PrimaryClientModeManagerChangedCallback()); |
| } |
| |
| // 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) { |
| // ignore if not primary |
| if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY) { |
| return; |
| } |
| |
| 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 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(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(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); |
| } |
| |
| private class PrimaryClientModeManagerChangedCallback |
| implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback { |
| |
| @Override |
| public void onChange( |
| @Nullable ConcreteClientModeManager prevPrimaryClientModeManager, |
| @Nullable ConcreteClientModeManager newPrimaryClientModeManager) { |
| // reset wifi lock on previous primary |
| if (prevPrimaryClientModeManager != null) { |
| resetCurrentMode(prevPrimaryClientModeManager); |
| } |
| // set wifi lock on new primary |
| if (newPrimaryClientModeManager != null) { |
| mWifiConnected = newPrimaryClientModeManager.isConnected(); |
| setNewMode(newPrimaryClientModeManager, getStrongestLockMode()); |
| } else { |
| mWifiConnected = false; |
| } |
| } |
| } |
| |
| /** 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(!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(!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(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; |
| } |
| } |
| } |