1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi; 18 19 import android.text.format.DateUtils; 20 import android.util.ArrayMap; 21 import android.util.Log; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.io.FileDescriptor; 26 import java.io.PrintWriter; 27 import java.util.Collection; 28 import java.util.Iterator; 29 import java.util.Map; 30 import java.util.Set; 31 32 /** 33 * A lock to determine whether Wifi Wake can re-enable Wifi. 34 * 35 * <p>Wakeuplock manages a list of networks to determine whether the device's location has changed. 36 */ 37 public class WakeupLock { 38 39 private static final String TAG = WakeupLock.class.getSimpleName(); 40 41 @VisibleForTesting 42 static final int CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 5; 43 @VisibleForTesting 44 static final long MAX_LOCK_TIME_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS; 45 46 private final WifiConfigManager mWifiConfigManager; 47 private final Map<ScanResultMatchInfo, Integer> mLockedNetworks = new ArrayMap<>(); 48 private final WifiWakeMetrics mWifiWakeMetrics; 49 private final Clock mClock; 50 51 private boolean mVerboseLoggingEnabled; 52 private long mLockTimestamp; 53 private boolean mIsInitialized; 54 private int mNumScans; 55 WakeupLock(WifiConfigManager wifiConfigManager, WifiWakeMetrics wifiWakeMetrics, Clock clock)56 public WakeupLock(WifiConfigManager wifiConfigManager, WifiWakeMetrics wifiWakeMetrics, 57 Clock clock) { 58 mWifiConfigManager = wifiConfigManager; 59 mWifiWakeMetrics = wifiWakeMetrics; 60 mClock = clock; 61 } 62 63 /** 64 * Sets the WakeupLock with the given {@link ScanResultMatchInfo} list. 65 * 66 * <p>This saves the wakeup lock to the store and begins the initialization process. 67 * 68 * @param scanResultList list of ScanResultMatchInfos to start the lock with 69 */ setLock(Collection<ScanResultMatchInfo> scanResultList)70 public void setLock(Collection<ScanResultMatchInfo> scanResultList) { 71 mLockTimestamp = mClock.getElapsedSinceBootMillis(); 72 mIsInitialized = false; 73 mNumScans = 0; 74 75 mLockedNetworks.clear(); 76 for (ScanResultMatchInfo scanResultMatchInfo : scanResultList) { 77 mLockedNetworks.put(scanResultMatchInfo, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT); 78 } 79 80 Log.d(TAG, "Lock set. Number of networks: " + mLockedNetworks.size()); 81 82 mWifiConfigManager.saveToStore(false /* forceWrite */); 83 } 84 85 /** 86 * Maybe sets the WakeupLock as initialized based on total scans handled. 87 * 88 * @param numScans total number of elapsed scans in the current WifiWake session 89 */ maybeSetInitializedByScans(int numScans)90 private void maybeSetInitializedByScans(int numScans) { 91 if (mIsInitialized) { 92 return; 93 } 94 boolean shouldBeInitialized = numScans >= CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; 95 if (shouldBeInitialized) { 96 mIsInitialized = true; 97 98 Log.d(TAG, "Lock initialized by handled scans. Scans: " + numScans); 99 if (mVerboseLoggingEnabled) { 100 Log.d(TAG, "State of lock: " + mLockedNetworks); 101 } 102 103 // log initialize event 104 mWifiWakeMetrics.recordInitializeEvent(mNumScans, mLockedNetworks.size()); 105 } 106 } 107 108 /** 109 * Maybe sets the WakeupLock as initialized based on elapsed time. 110 * 111 * @param timestampMillis current timestamp 112 */ maybeSetInitializedByTimeout(long timestampMillis)113 private void maybeSetInitializedByTimeout(long timestampMillis) { 114 if (mIsInitialized) { 115 return; 116 } 117 long elapsedTime = timestampMillis - mLockTimestamp; 118 boolean shouldBeInitialized = elapsedTime > MAX_LOCK_TIME_MILLIS; 119 120 if (shouldBeInitialized) { 121 mIsInitialized = true; 122 123 Log.d(TAG, "Lock initialized by timeout. Elapsed time: " + elapsedTime); 124 if (mNumScans == 0) { 125 Log.w(TAG, "Lock initialized with 0 handled scans!"); 126 } 127 if (mVerboseLoggingEnabled) { 128 Log.d(TAG, "State of lock: " + mLockedNetworks); 129 } 130 131 // log initialize event 132 mWifiWakeMetrics.recordInitializeEvent(mNumScans, mLockedNetworks.size()); 133 } 134 } 135 136 /** Returns whether the lock has been fully initialized. */ isInitialized()137 public boolean isInitialized() { 138 return mIsInitialized; 139 } 140 141 /** 142 * Adds the given networks to the lock. 143 * 144 * <p>This is called during the initialization step. 145 * 146 * @param networkList The list of networks to be added 147 */ addToLock(Collection<ScanResultMatchInfo> networkList)148 private void addToLock(Collection<ScanResultMatchInfo> networkList) { 149 if (mVerboseLoggingEnabled) { 150 Log.d(TAG, "Initializing lock with networks: " + networkList); 151 } 152 153 boolean hasChanged = false; 154 155 for (ScanResultMatchInfo network : networkList) { 156 if (!mLockedNetworks.containsKey(network)) { 157 mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT); 158 hasChanged = true; 159 } 160 } 161 162 if (hasChanged) { 163 mWifiConfigManager.saveToStore(false /* forceWrite */); 164 } 165 166 // Set initialized if the lock has handled enough scans, and log the event 167 maybeSetInitializedByScans(mNumScans); 168 } 169 170 /** 171 * Removes networks from the lock if not present in the given {@link ScanResultMatchInfo} list. 172 * 173 * <p>If a network in the lock is not present in the list, reduce the number of scans 174 * required to evict by one. Remove any entries in the list with 0 scans required to evict. If 175 * any entries in the lock are removed, the store is updated. 176 * 177 * @param networkList list of present ScanResultMatchInfos to update the lock with 178 */ removeFromLock(Collection<ScanResultMatchInfo> networkList)179 private void removeFromLock(Collection<ScanResultMatchInfo> networkList) { 180 if (mVerboseLoggingEnabled) { 181 Log.d(TAG, "Filtering lock with networks: " + networkList); 182 } 183 184 boolean hasChanged = false; 185 Iterator<Map.Entry<ScanResultMatchInfo, Integer>> it = 186 mLockedNetworks.entrySet().iterator(); 187 while (it.hasNext()) { 188 Map.Entry<ScanResultMatchInfo, Integer> entry = it.next(); 189 190 // if present in scan list, reset to max 191 if (networkList.contains(entry.getKey())) { 192 if (mVerboseLoggingEnabled) { 193 Log.d(TAG, "Found network in lock: " + entry.getKey().networkSsid); 194 } 195 entry.setValue(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT); 196 continue; 197 } 198 199 // decrement and remove if necessary 200 entry.setValue(entry.getValue() - 1); 201 if (entry.getValue() <= 0) { 202 Log.d(TAG, "Removed network from lock: " + entry.getKey().networkSsid); 203 it.remove(); 204 hasChanged = true; 205 } 206 } 207 208 if (hasChanged) { 209 mWifiConfigManager.saveToStore(false /* forceWrite */); 210 } 211 212 if (isUnlocked()) { 213 Log.d(TAG, "Lock emptied. Recording unlock event."); 214 mWifiWakeMetrics.recordUnlockEvent(mNumScans); 215 } 216 } 217 218 /** 219 * Updates the lock with the given {@link ScanResultMatchInfo} list. 220 * 221 * <p>Based on the current initialization state of the lock, either adds or removes networks 222 * from the lock. 223 * 224 * <p>The lock is initialized after {@link #CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} 225 * scans have been handled, or after {@link #MAX_LOCK_TIME_MILLIS} milliseconds have elapsed 226 * since {@link #setLock(Collection)}. 227 * 228 * @param networkList list of present ScanResultMatchInfos to update the lock with 229 */ update(Collection<ScanResultMatchInfo> networkList)230 public void update(Collection<ScanResultMatchInfo> networkList) { 231 // update is no-op if already unlocked 232 if (isUnlocked()) { 233 return; 234 } 235 // Before checking handling the scan, we check to see whether we've exceeded the maximum 236 // time allowed for initialization. If so, we set initialized and treat this scan as a 237 // "removeFromLock()" instead of an "addToLock()". 238 maybeSetInitializedByTimeout(mClock.getElapsedSinceBootMillis()); 239 240 mNumScans++; 241 242 // add or remove networks based on initialized status 243 if (mIsInitialized) { 244 removeFromLock(networkList); 245 } else { 246 addToLock(networkList); 247 } 248 } 249 250 /** Returns whether the WakeupLock is unlocked */ isUnlocked()251 public boolean isUnlocked() { 252 return mIsInitialized && mLockedNetworks.isEmpty(); 253 } 254 255 /** Returns the data source for the WakeupLock config store data. */ getDataSource()256 public WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> getDataSource() { 257 return new WakeupLockDataSource(); 258 } 259 260 /** Dumps wakeup lock contents. */ dump(FileDescriptor fd, PrintWriter pw, String[] args)261 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 262 pw.println("WakeupLock: "); 263 pw.println("mNumScans: " + mNumScans); 264 pw.println("mIsInitialized: " + mIsInitialized); 265 pw.println("Locked networks: " + mLockedNetworks.size()); 266 for (Map.Entry<ScanResultMatchInfo, Integer> entry : mLockedNetworks.entrySet()) { 267 pw.println(entry.getKey() + ", scans to evict: " + entry.getValue()); 268 } 269 } 270 271 /** Set whether verbose logging is enabled. */ enableVerboseLogging(boolean enabled)272 public void enableVerboseLogging(boolean enabled) { 273 mVerboseLoggingEnabled = enabled; 274 } 275 276 private class WakeupLockDataSource 277 implements WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> { 278 279 @Override getData()280 public Set<ScanResultMatchInfo> getData() { 281 return mLockedNetworks.keySet(); 282 } 283 284 @Override setData(Set<ScanResultMatchInfo> data)285 public void setData(Set<ScanResultMatchInfo> data) { 286 mLockedNetworks.clear(); 287 for (ScanResultMatchInfo network : data) { 288 mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT); 289 } 290 // lock is considered initialized if loaded from store 291 mIsInitialized = true; 292 } 293 } 294 } 295