blob: b1b3f112d00c4b9df7105b0e470c730a583498fe [file] [log] [blame]
/*
* 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.content.Context;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.WorkSource;
import android.util.Slog;
import com.android.internal.app.IBatteryStats;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* WifiLockManager maintains the list of wake locks held by different applications.
*/
public class WifiLockManager {
private static final String TAG = "WifiLockManager";
private boolean mVerboseLoggingEnabled = false;
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final List<WifiLock> mWifiLocks = new ArrayList<>();
// some wifi lock statistics
private int mFullHighPerfLocksAcquired;
private int mFullHighPerfLocksReleased;
private int mFullLocksAcquired;
private int mFullLocksReleased;
private int mScanLocksAcquired;
private int mScanLocksReleased;
WifiLockManager(Context context, IBatteryStats batteryStats) {
mContext = context;
mBatteryStats = batteryStats;
}
/**
* Method allowing a calling app to acquire a Wifi WakeLock in the supplied mode.
*
* This method verifies that the caller has permission to make the call and 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) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
if (!isValidLockMode(lockMode)) {
throw new IllegalArgumentException("lockMode =" + lockMode);
}
if (ws == null || ws.isEmpty()) {
ws = new WorkSource(Binder.getCallingUid());
} else {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
}
return addLock(new WifiLock(lockMode, tag, binder, ws));
}
/**
* Method used by applications to release a WiFi Wake lock. This method checks permissions for
* the caller and if allowed, releases the underlying WifiLock(s).
*
* @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) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
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 (mWifiLocks.isEmpty()) {
return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
}
if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
}
if (mFullLocksAcquired > mFullLocksReleased) {
return WifiManager.WIFI_MODE_FULL;
}
return WifiManager.WIFI_MODE_SCAN_ONLY;
}
/**
* 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) {
// Does the caller have permission to make this call?
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
// Now check if there is an active lock
WifiLock wl = findLockByBinder(binder);
if (wl == null) {
throw new IllegalArgumentException("Wifi lock not active");
}
WorkSource newWorkSource;
if (ws == null || ws.isEmpty()) {
newWorkSource = new WorkSource(Binder.getCallingUid());
} else {
// Make a copy of the WorkSource before adding it to the WakeLock
newWorkSource = new WorkSource(ws);
}
if (mVerboseLoggingEnabled) {
Slog.d(TAG, "updateWifiLockWakeSource: " + wl + ", newWorkSource=" + newWorkSource);
}
long ident = Binder.clearCallingIdentity();
try {
// 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. BatteryStats can correctly match
// "nested" acquire / release pairs.
mBatteryStats.noteFullWifiLockAcquiredFromSource(newWorkSource);
mBatteryStats.noteFullWifiLockReleasedFromSource(wl.mWorkSource);
wl.mWorkSource = newWorkSource;
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private static boolean isValidLockMode(int lockMode) {
if (lockMode != WifiManager.WIFI_MODE_FULL
&& lockMode != WifiManager.WIFI_MODE_SCAN_ONLY
&& lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
return false;
}
return true;
}
private synchronized boolean addLock(WifiLock lock) {
if (mVerboseLoggingEnabled) {
Slog.d(TAG, "addLock: " + lock);
}
if (findLockByBinder(lock.getBinder()) != null) {
if (mVerboseLoggingEnabled) {
Slog.d(TAG, "attempted to add a lock when already holding one");
}
return false;
}
mWifiLocks.add(lock);
boolean lockAdded = false;
long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.noteFullWifiLockAcquiredFromSource(lock.mWorkSource);
switch(lock.mMode) {
case WifiManager.WIFI_MODE_FULL:
++mFullLocksAcquired;
break;
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
++mFullHighPerfLocksAcquired;
break;
case WifiManager.WIFI_MODE_SCAN_ONLY:
++mScanLocksAcquired;
break;
}
lockAdded = true;
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
}
return lockAdded;
}
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 is not active.
return false;
}
if (mVerboseLoggingEnabled) {
Slog.d(TAG, "releaseLock: " + wifiLock);
}
long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
switch(wifiLock.mMode) {
case WifiManager.WIFI_MODE_FULL:
++mFullLocksReleased;
break;
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
++mFullHighPerfLocksReleased;
break;
case WifiManager.WIFI_MODE_SCAN_ONLY:
++mScanLocksReleased;
break;
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
private synchronized WifiLock findLockByBinder(IBinder binder) {
for (WifiLock lock : mWifiLocks) {
if (lock.getBinder() == binder) {
return lock;
}
}
return null;
}
protected void dump(PrintWriter pw) {
pw.println("Locks acquired: " + mFullLocksAcquired + " full, "
+ mFullHighPerfLocksAcquired + " full high perf, "
+ mScanLocksAcquired + " scan");
pw.println("Locks released: " + mFullLocksReleased + " full, "
+ mFullHighPerfLocksReleased + " full high perf, "
+ mScanLocksReleased + " scan");
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;
WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
mTag = tag;
mBinder = binder;
mUid = Binder.getCallingUid();
mMode = lockMode;
mWorkSource = ws;
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
}
protected WorkSource getWorkSource() {
return mWorkSource;
}
protected int getUid() {
return mUid;
}
protected IBinder getBinder() {
return mBinder;
}
public void binderDied() {
releaseLock(mBinder);
}
public void unlinkDeathRecipient() {
mBinder.unlinkToDeath(this, 0);
}
public String toString() {
return "WifiLock{" + this.mTag + " type=" + this.mMode + " uid=" + mUid
+ " workSource=" + mWorkSource + "}";
}
}
}