1 /* 2 * Copyright (C) 2019 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.content.Context; 20 import android.net.MacAddress; 21 import android.net.wifi.WifiInfo; 22 import android.net.wifi.nl80211.WifiNl80211Manager; 23 import android.os.Handler; 24 import android.util.Log; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.server.wifi.util.TimedQuotaManager; 28 import com.android.wifi.resources.R; 29 30 import java.io.FileDescriptor; 31 import java.io.PrintWriter; 32 import java.time.Duration; 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * Tracks state that decides if a link probe should be performed. If so, trigger a link probe to 38 * evaluate connection quality. 39 */ 40 public class LinkProbeManager { 41 private static final String TAG = "WifiLinkProbeManager"; 42 43 private static final int WIFI_LINK_PROBING_ENABLED_DEFAULT = 1; // 1 = enabled 44 45 // TODO(112029045): Use constants from ScoringParams instead 46 @VisibleForTesting static final int RSSI_THRESHOLD = -70; 47 @VisibleForTesting static final int LINK_SPEED_THRESHOLD_MBPS = 15; // in megabits per second 48 /** Minimum delay before probing after the last probe. */ 49 @VisibleForTesting static final long DELAY_BETWEEN_PROBES_MS = 6000; 50 /** Minimum delay before probing after screen turned on. */ 51 @VisibleForTesting static final long SCREEN_ON_DELAY_MS = 6000; 52 /** 53 * Minimum delay before probing after last increase of the Tx success counter (which indicates 54 * that a data frame (i.e. not counting management frame) was successfully transmitted). 55 */ 56 @VisibleForTesting static final long DELAY_AFTER_TX_SUCCESS_MS = 6000; 57 58 @VisibleForTesting static final long MAX_PROBE_COUNT_IN_PERIOD = 59 WifiMetrics.MAX_LINK_PROBE_STA_EVENTS; 60 @VisibleForTesting static final long PERIOD_MILLIS = Duration.ofDays(1).toMillis(); 61 62 @VisibleForTesting static final int[] EXPERIMENT_DELAYS_MS = {3000, 6000, 9000, 12000, 15000}; 63 @VisibleForTesting static final int[] EXPERIMENT_RSSIS = {-65, -70, -75}; 64 @VisibleForTesting static final int[] EXPERIMENT_LINK_SPEEDS = {10, 15, 20}; 65 private List<Experiment> mExperiments = new ArrayList<>(); 66 67 private final Clock mClock; 68 private final WifiNative mWifiNative; 69 private final WifiMetrics mWifiMetrics; 70 private final FrameworkFacade mFrameworkFacade; 71 private final Handler mHandler; 72 private final Context mContext; 73 74 private Boolean mLinkProbingSupported = null; 75 76 private boolean mVerboseLoggingEnabled = false; 77 78 /** 79 * Tracks the last timestamp when a link probe was triggered. Link probing only occurs when at 80 * least {@link #DELAY_BETWEEN_PROBES_MS} has passed since the last link probe. 81 */ 82 private long mLastLinkProbeTimestampMs; 83 /** 84 * Tracks the last timestamp when {@link WifiInfo#txSuccess} was increased i.e. the last time a 85 * Tx was successful. Link probing only occurs when at least {@link #DELAY_AFTER_TX_SUCCESS_MS} 86 * has passed since the last Tx success. 87 * This is also reset to the current time when {@link #resetOnNewConnection()} is called, so 88 * that a link probe only occurs at least {@link #DELAY_AFTER_TX_SUCCESS_MS} after a new 89 * connection is made. 90 */ 91 private long mLastTxSuccessIncreaseTimestampMs; 92 /** 93 * Stores the last value of {@link WifiInfo#txSuccess}. The current value of 94 * {@link WifiInfo#txSuccess} is compared against the last value to determine whether there was 95 * a successful Tx. 96 */ 97 private long mLastTxSuccessCount; 98 /** 99 * Tracks the last timestamp when the screen turned on. Link probing only occurs when at least 100 * {@link #SCREEN_ON_DELAY_MS} has passed since the last time the screen was turned on. 101 */ 102 private long mLastScreenOnTimestampMs; 103 private final TimedQuotaManager mTimedQuotaManager; 104 LinkProbeManager(Clock clock, WifiNative wifiNative, WifiMetrics wifiMetrics, FrameworkFacade frameworkFacade, Handler handler, Context context)105 public LinkProbeManager(Clock clock, WifiNative wifiNative, WifiMetrics wifiMetrics, 106 FrameworkFacade frameworkFacade, Handler handler, Context context) { 107 mClock = clock; 108 mWifiNative = wifiNative; 109 mWifiMetrics = wifiMetrics; 110 mFrameworkFacade = frameworkFacade; 111 mHandler = handler; 112 mContext = context; 113 mTimedQuotaManager = new TimedQuotaManager(clock, MAX_PROBE_COUNT_IN_PERIOD, PERIOD_MILLIS); 114 115 initExperiments(); 116 } 117 isLinkProbingSupported()118 private boolean isLinkProbingSupported() { 119 if (mLinkProbingSupported == null) { 120 mLinkProbingSupported = mContext.getResources() 121 .getBoolean(R.bool.config_wifi_link_probing_supported); 122 if (mLinkProbingSupported) { 123 resetOnNewConnection(); 124 resetOnScreenTurnedOn(); 125 } 126 } 127 return mLinkProbingSupported; 128 } 129 130 /** enables/disables wifi verbose logging */ enableVerboseLogging(boolean enable)131 public void enableVerboseLogging(boolean enable) { 132 mVerboseLoggingEnabled = enable; 133 } 134 135 /** dumps internal state */ dump(FileDescriptor fd, PrintWriter pw, String[] args)136 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 137 pw.println("Dump of LinkProbeManager"); 138 pw.println("LinkProbeManager - link probing supported by device: " 139 + isLinkProbingSupported()); 140 pw.println("LinkProbeManager - mLastLinkProbeTimestampMs: " + mLastLinkProbeTimestampMs); 141 pw.println("LinkProbeManager - mLastTxSuccessIncreaseTimestampMs: " 142 + mLastTxSuccessIncreaseTimestampMs); 143 pw.println("LinkProbeManager - mLastTxSuccessCount: " + mLastTxSuccessCount); 144 pw.println("LinkProbeManager - mLastScreenOnTimestampMs: " + mLastScreenOnTimestampMs); 145 pw.println("LinkProbeManager - mTimedQuotaManager: " + mTimedQuotaManager); 146 } 147 148 /** 149 * When connecting to a new network, reset internal state. 150 */ resetOnNewConnection()151 public void resetOnNewConnection() { 152 mExperiments.forEach(Experiment::resetOnNewConnection); 153 if (!isLinkProbingSupported()) return; 154 155 long now = mClock.getElapsedSinceBootMillis(); 156 mLastLinkProbeTimestampMs = now; 157 mLastTxSuccessIncreaseTimestampMs = now; 158 mLastTxSuccessCount = 0; 159 } 160 161 /** 162 * When RSSI poll events are stopped and restarted (usually screen turned off then back on), 163 * reset internal state. 164 */ resetOnScreenTurnedOn()165 public void resetOnScreenTurnedOn() { 166 mExperiments.forEach(Experiment::resetOnScreenTurnedOn); 167 if (!isLinkProbingSupported()) return; 168 169 mLastScreenOnTimestampMs = mClock.getElapsedSinceBootMillis(); 170 } 171 172 /** 173 * Based on network conditions provided by WifiInfo, decides if a link probe should be 174 * performed. If so, trigger a link probe and report the results to WifiMetrics. 175 * 176 * @param wifiInfo the updated WifiInfo 177 * @param interfaceName the interface that the link probe should be performed on, if applicable. 178 */ updateConnectionStats(WifiInfo wifiInfo, String interfaceName)179 public void updateConnectionStats(WifiInfo wifiInfo, String interfaceName) { 180 mExperiments.forEach(e -> e.updateConnectionStats(wifiInfo)); 181 182 if (!isLinkProbingSupported()) return; 183 184 long now = mClock.getElapsedSinceBootMillis(); 185 186 // at least 1 tx succeeded since last update 187 if (mLastTxSuccessCount < wifiInfo.txSuccess) { 188 mLastTxSuccessIncreaseTimestampMs = now; 189 } 190 mLastTxSuccessCount = wifiInfo.txSuccess; 191 192 // maximum 1 link probe every DELAY_BETWEEN_PROBES_MS 193 long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; 194 if (timeSinceLastLinkProbeMs < DELAY_BETWEEN_PROBES_MS) { 195 return; 196 } 197 198 // if tx succeeded at least once in the last DELAY_AFTER_TX_SUCCESS_MS, don't need to probe 199 long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; 200 if (timeSinceLastTxSuccessIncreaseMs < DELAY_AFTER_TX_SUCCESS_MS) { 201 return; 202 } 203 204 // if not enough time has passed since the screen last turned on, don't probe 205 long timeSinceLastScreenOnMs = now - mLastScreenOnTimestampMs; 206 if (timeSinceLastScreenOnMs < SCREEN_ON_DELAY_MS) { 207 return; 208 } 209 210 // can skip probing if RSSI is valid and high and link speed is fast 211 int rssi = wifiInfo.getRssi(); 212 int linkSpeed = wifiInfo.getLinkSpeed(); 213 if (rssi != WifiInfo.INVALID_RSSI && rssi > RSSI_THRESHOLD 214 && linkSpeed > LINK_SPEED_THRESHOLD_MBPS) { 215 return; 216 } 217 218 if (!mTimedQuotaManager.requestQuota()) { 219 return; 220 } 221 222 if (mVerboseLoggingEnabled) { 223 Log.d(TAG, "link probing triggered with conditions: timeSinceLastLinkProbeMs=" 224 + timeSinceLastLinkProbeMs 225 + " timeSinceLastTxSuccessIncreaseMs=" + timeSinceLastTxSuccessIncreaseMs 226 + " rssi=" + rssi + " linkSpeed=" + linkSpeed); 227 } 228 229 // TODO(b/112029045): also report MCS rate to metrics when supported by driver 230 mWifiNative.probeLink( 231 interfaceName, 232 MacAddress.fromString(wifiInfo.getBSSID()), 233 new WifiNl80211Manager.SendMgmtFrameCallback() { 234 @Override 235 public void onAck(int elapsedTimeMs) { 236 if (mVerboseLoggingEnabled) { 237 Log.d(TAG, "link probing success, elapsedTimeMs=" 238 + elapsedTimeMs); 239 } 240 mWifiMetrics.logLinkProbeSuccess(interfaceName, 241 timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, elapsedTimeMs); 242 } 243 244 @Override 245 public void onFailure(int reason) { 246 if (mVerboseLoggingEnabled) { 247 Log.d(TAG, "link probing failure, reason=" + reason); 248 } 249 mWifiMetrics.logLinkProbeFailure(interfaceName, 250 timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, reason); 251 } 252 }, 253 -1); // placeholder, lets driver determine MCS rate 254 mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); 255 } 256 initExperiments()257 private void initExperiments() { 258 for (int delay : EXPERIMENT_DELAYS_MS) { 259 for (int rssiThreshold : EXPERIMENT_RSSIS) { 260 for (int linkSpeedThreshold: EXPERIMENT_LINK_SPEEDS) { 261 Experiment experiment = new Experiment(mClock, mWifiMetrics, 262 delay, delay, delay, rssiThreshold, linkSpeedThreshold); 263 mExperiments.add(experiment); 264 } 265 } 266 } 267 } 268 269 // TODO(b/131091030): remove once experiment is over 270 private static class Experiment { 271 272 private final Clock mClock; 273 private final WifiMetrics mWifiMetrics; 274 private final int mScreenOnDelayMs; 275 private final int mNoTxDelayMs; 276 private final int mDelayBetweenProbesMs; 277 private final int mRssiThreshold; 278 private final int mLinkSpeedThreshold; 279 private final String mExperimentId; 280 281 private long mLastLinkProbeTimestampMs; 282 private long mLastTxSuccessIncreaseTimestampMs; 283 private long mLastTxSuccessCount; 284 private long mLastScreenOnTimestampMs; 285 Experiment(Clock clock, WifiMetrics wifiMetrics, int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs, int rssiThreshold, int linkSpeedThreshold)286 Experiment(Clock clock, WifiMetrics wifiMetrics, 287 int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs, 288 int rssiThreshold, int linkSpeedThreshold) { 289 mClock = clock; 290 mWifiMetrics = wifiMetrics; 291 mScreenOnDelayMs = screenOnDelayMs; 292 mNoTxDelayMs = noTxDelayMs; 293 mDelayBetweenProbesMs = delayBetweenProbesMs; 294 mRssiThreshold = rssiThreshold; 295 mLinkSpeedThreshold = linkSpeedThreshold; 296 297 mExperimentId = getExperimentId(); 298 299 resetOnNewConnection(); 300 resetOnScreenTurnedOn(); 301 } 302 getExperimentId()303 private String getExperimentId() { 304 return "[screenOnDelay=" + mScreenOnDelayMs + ',' 305 + "noTxDelay=" + mNoTxDelayMs + ',' 306 + "delayBetweenProbes=" + mDelayBetweenProbesMs + ',' 307 + "rssiThreshold=" + mRssiThreshold + ',' 308 + "linkSpeedThreshold=" + mLinkSpeedThreshold + ']'; 309 } 310 resetOnNewConnection()311 void resetOnNewConnection() { 312 long now = mClock.getElapsedSinceBootMillis(); 313 mLastLinkProbeTimestampMs = now; 314 mLastTxSuccessIncreaseTimestampMs = now; 315 mLastTxSuccessCount = 0; 316 } 317 resetOnScreenTurnedOn()318 void resetOnScreenTurnedOn() { 319 mLastScreenOnTimestampMs = mClock.getElapsedSinceBootMillis(); 320 } 321 updateConnectionStats(WifiInfo wifiInfo)322 void updateConnectionStats(WifiInfo wifiInfo) { 323 long now = mClock.getElapsedSinceBootMillis(); 324 325 if (mLastTxSuccessCount < wifiInfo.txSuccess) { 326 mLastTxSuccessIncreaseTimestampMs = now; 327 } 328 mLastTxSuccessCount = wifiInfo.txSuccess; 329 330 long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; 331 if (timeSinceLastLinkProbeMs < mDelayBetweenProbesMs) { 332 return; 333 } 334 335 // if tx succeeded at least once in the last LINK_PROBE_INTERVAL_MS, don't need to probe 336 long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; 337 if (timeSinceLastTxSuccessIncreaseMs < mNoTxDelayMs) { 338 return; 339 } 340 341 long timeSinceLastScreenOnMs = now - mLastScreenOnTimestampMs; 342 if (timeSinceLastScreenOnMs < SCREEN_ON_DELAY_MS) { 343 return; 344 } 345 346 // can skip probing if RSSI is valid and high and link speed is fast 347 int rssi = wifiInfo.getRssi(); 348 int linkSpeed = wifiInfo.getLinkSpeed(); 349 if (rssi != WifiInfo.INVALID_RSSI && rssi > mRssiThreshold 350 && linkSpeed > mLinkSpeedThreshold) { 351 return; 352 } 353 354 mWifiMetrics.incrementLinkProbeExperimentProbeCount(mExperimentId); 355 356 mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); 357 } 358 } 359 } 360