295 lines
10 KiB
Java
295 lines
10 KiB
Java
/*
|
|
* Copyright 2017 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.text.format.DateUtils;
|
|
import android.util.ArrayMap;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* A lock to determine whether Wifi Wake can re-enable Wifi.
|
|
*
|
|
* <p>Wakeuplock manages a list of networks to determine whether the device's location has changed.
|
|
*/
|
|
public class WakeupLock {
|
|
|
|
private static final String TAG = WakeupLock.class.getSimpleName();
|
|
|
|
@VisibleForTesting
|
|
static final int CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 5;
|
|
@VisibleForTesting
|
|
static final long MAX_LOCK_TIME_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
|
|
|
|
private final WifiConfigManager mWifiConfigManager;
|
|
private final Map<ScanResultMatchInfo, Integer> mLockedNetworks = new ArrayMap<>();
|
|
private final WifiWakeMetrics mWifiWakeMetrics;
|
|
private final Clock mClock;
|
|
|
|
private boolean mVerboseLoggingEnabled;
|
|
private long mLockTimestamp;
|
|
private boolean mIsInitialized;
|
|
private int mNumScans;
|
|
|
|
public WakeupLock(WifiConfigManager wifiConfigManager, WifiWakeMetrics wifiWakeMetrics,
|
|
Clock clock) {
|
|
mWifiConfigManager = wifiConfigManager;
|
|
mWifiWakeMetrics = wifiWakeMetrics;
|
|
mClock = clock;
|
|
}
|
|
|
|
/**
|
|
* Sets the WakeupLock with the given {@link ScanResultMatchInfo} list.
|
|
*
|
|
* <p>This saves the wakeup lock to the store and begins the initialization process.
|
|
*
|
|
* @param scanResultList list of ScanResultMatchInfos to start the lock with
|
|
*/
|
|
public void setLock(Collection<ScanResultMatchInfo> scanResultList) {
|
|
mLockTimestamp = mClock.getElapsedSinceBootMillis();
|
|
mIsInitialized = false;
|
|
mNumScans = 0;
|
|
|
|
mLockedNetworks.clear();
|
|
for (ScanResultMatchInfo scanResultMatchInfo : scanResultList) {
|
|
mLockedNetworks.put(scanResultMatchInfo, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
|
|
}
|
|
|
|
Log.d(TAG, "Lock set. Number of networks: " + mLockedNetworks.size());
|
|
|
|
mWifiConfigManager.saveToStore(false /* forceWrite */);
|
|
}
|
|
|
|
/**
|
|
* Maybe sets the WakeupLock as initialized based on total scans handled.
|
|
*
|
|
* @param numScans total number of elapsed scans in the current WifiWake session
|
|
*/
|
|
private void maybeSetInitializedByScans(int numScans) {
|
|
if (mIsInitialized) {
|
|
return;
|
|
}
|
|
boolean shouldBeInitialized = numScans >= CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT;
|
|
if (shouldBeInitialized) {
|
|
mIsInitialized = true;
|
|
|
|
Log.d(TAG, "Lock initialized by handled scans. Scans: " + numScans);
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "State of lock: " + mLockedNetworks);
|
|
}
|
|
|
|
// log initialize event
|
|
mWifiWakeMetrics.recordInitializeEvent(mNumScans, mLockedNetworks.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maybe sets the WakeupLock as initialized based on elapsed time.
|
|
*
|
|
* @param timestampMillis current timestamp
|
|
*/
|
|
private void maybeSetInitializedByTimeout(long timestampMillis) {
|
|
if (mIsInitialized) {
|
|
return;
|
|
}
|
|
long elapsedTime = timestampMillis - mLockTimestamp;
|
|
boolean shouldBeInitialized = elapsedTime > MAX_LOCK_TIME_MILLIS;
|
|
|
|
if (shouldBeInitialized) {
|
|
mIsInitialized = true;
|
|
|
|
Log.d(TAG, "Lock initialized by timeout. Elapsed time: " + elapsedTime);
|
|
if (mNumScans == 0) {
|
|
Log.w(TAG, "Lock initialized with 0 handled scans!");
|
|
}
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "State of lock: " + mLockedNetworks);
|
|
}
|
|
|
|
// log initialize event
|
|
mWifiWakeMetrics.recordInitializeEvent(mNumScans, mLockedNetworks.size());
|
|
}
|
|
}
|
|
|
|
/** Returns whether the lock has been fully initialized. */
|
|
public boolean isInitialized() {
|
|
return mIsInitialized;
|
|
}
|
|
|
|
/**
|
|
* Adds the given networks to the lock.
|
|
*
|
|
* <p>This is called during the initialization step.
|
|
*
|
|
* @param networkList The list of networks to be added
|
|
*/
|
|
private void addToLock(Collection<ScanResultMatchInfo> networkList) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "Initializing lock with networks: " + networkList);
|
|
}
|
|
|
|
boolean hasChanged = false;
|
|
|
|
for (ScanResultMatchInfo network : networkList) {
|
|
if (!mLockedNetworks.containsKey(network)) {
|
|
mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
|
|
hasChanged = true;
|
|
}
|
|
}
|
|
|
|
if (hasChanged) {
|
|
mWifiConfigManager.saveToStore(false /* forceWrite */);
|
|
}
|
|
|
|
// Set initialized if the lock has handled enough scans, and log the event
|
|
maybeSetInitializedByScans(mNumScans);
|
|
}
|
|
|
|
/**
|
|
* Removes networks from the lock if not present in the given {@link ScanResultMatchInfo} list.
|
|
*
|
|
* <p>If a network in the lock is not present in the list, reduce the number of scans
|
|
* required to evict by one. Remove any entries in the list with 0 scans required to evict. If
|
|
* any entries in the lock are removed, the store is updated.
|
|
*
|
|
* @param networkList list of present ScanResultMatchInfos to update the lock with
|
|
*/
|
|
private void removeFromLock(Collection<ScanResultMatchInfo> networkList) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "Filtering lock with networks: " + networkList);
|
|
}
|
|
|
|
boolean hasChanged = false;
|
|
Iterator<Map.Entry<ScanResultMatchInfo, Integer>> it =
|
|
mLockedNetworks.entrySet().iterator();
|
|
while (it.hasNext()) {
|
|
Map.Entry<ScanResultMatchInfo, Integer> entry = it.next();
|
|
|
|
// if present in scan list, reset to max
|
|
if (networkList.contains(entry.getKey())) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "Found network in lock: " + entry.getKey().networkSsid);
|
|
}
|
|
entry.setValue(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
|
|
continue;
|
|
}
|
|
|
|
// decrement and remove if necessary
|
|
entry.setValue(entry.getValue() - 1);
|
|
if (entry.getValue() <= 0) {
|
|
Log.d(TAG, "Removed network from lock: " + entry.getKey().networkSsid);
|
|
it.remove();
|
|
hasChanged = true;
|
|
}
|
|
}
|
|
|
|
if (hasChanged) {
|
|
mWifiConfigManager.saveToStore(false /* forceWrite */);
|
|
}
|
|
|
|
if (isUnlocked()) {
|
|
Log.d(TAG, "Lock emptied. Recording unlock event.");
|
|
mWifiWakeMetrics.recordUnlockEvent(mNumScans);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the lock with the given {@link ScanResultMatchInfo} list.
|
|
*
|
|
* <p>Based on the current initialization state of the lock, either adds or removes networks
|
|
* from the lock.
|
|
*
|
|
* <p>The lock is initialized after {@link #CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT}
|
|
* scans have been handled, or after {@link #MAX_LOCK_TIME_MILLIS} milliseconds have elapsed
|
|
* since {@link #setLock(Collection)}.
|
|
*
|
|
* @param networkList list of present ScanResultMatchInfos to update the lock with
|
|
*/
|
|
public void update(Collection<ScanResultMatchInfo> networkList) {
|
|
// update is no-op if already unlocked
|
|
if (isUnlocked()) {
|
|
return;
|
|
}
|
|
// Before checking handling the scan, we check to see whether we've exceeded the maximum
|
|
// time allowed for initialization. If so, we set initialized and treat this scan as a
|
|
// "removeFromLock()" instead of an "addToLock()".
|
|
maybeSetInitializedByTimeout(mClock.getElapsedSinceBootMillis());
|
|
|
|
mNumScans++;
|
|
|
|
// add or remove networks based on initialized status
|
|
if (mIsInitialized) {
|
|
removeFromLock(networkList);
|
|
} else {
|
|
addToLock(networkList);
|
|
}
|
|
}
|
|
|
|
/** Returns whether the WakeupLock is unlocked */
|
|
public boolean isUnlocked() {
|
|
return mIsInitialized && mLockedNetworks.isEmpty();
|
|
}
|
|
|
|
/** Returns the data source for the WakeupLock config store data. */
|
|
public WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> getDataSource() {
|
|
return new WakeupLockDataSource();
|
|
}
|
|
|
|
/** Dumps wakeup lock contents. */
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
pw.println("WakeupLock: ");
|
|
pw.println("mNumScans: " + mNumScans);
|
|
pw.println("mIsInitialized: " + mIsInitialized);
|
|
pw.println("Locked networks: " + mLockedNetworks.size());
|
|
for (Map.Entry<ScanResultMatchInfo, Integer> entry : mLockedNetworks.entrySet()) {
|
|
pw.println(entry.getKey() + ", scans to evict: " + entry.getValue());
|
|
}
|
|
}
|
|
|
|
/** Set whether verbose logging is enabled. */
|
|
public void enableVerboseLogging(boolean enabled) {
|
|
mVerboseLoggingEnabled = enabled;
|
|
}
|
|
|
|
private class WakeupLockDataSource
|
|
implements WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> {
|
|
|
|
@Override
|
|
public Set<ScanResultMatchInfo> getData() {
|
|
return mLockedNetworks.keySet();
|
|
}
|
|
|
|
@Override
|
|
public void setData(Set<ScanResultMatchInfo> data) {
|
|
mLockedNetworks.clear();
|
|
for (ScanResultMatchInfo network : data) {
|
|
mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
|
|
}
|
|
// lock is considered initialized if loaded from store
|
|
mIsInitialized = true;
|
|
}
|
|
}
|
|
}
|