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, String.format( 224 "link probing triggered with conditions: timeSinceLastLinkProbeMs=%d " 225 + "timeSinceLastTxSuccessIncreaseMs=%d rssi=%d linkSpeed=%s", 226 timeSinceLastLinkProbeMs, timeSinceLastTxSuccessIncreaseMs, 227 rssi, linkSpeed)); 228 } 229 230 // TODO(b/112029045): also report MCS rate to metrics when supported by driver 231 mWifiNative.probeLink( 232 interfaceName, 233 MacAddress.fromString(wifiInfo.getBSSID()), 234 new WifiNl80211Manager.SendMgmtFrameCallback() { 235 @Override 236 public void onAck(int elapsedTimeMs) { 237 if (mVerboseLoggingEnabled) { 238 Log.d(TAG, "link probing success, elapsedTimeMs=" 239 + elapsedTimeMs); 240 } 241 mWifiMetrics.logLinkProbeSuccess(interfaceName, 242 timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, elapsedTimeMs); 243 } 244 245 @Override 246 public void onFailure(int reason) { 247 if (mVerboseLoggingEnabled) { 248 Log.d(TAG, "link probing failure, reason=" + reason); 249 } 250 mWifiMetrics.logLinkProbeFailure(interfaceName, 251 timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, reason); 252 } 253 }, 254 -1); // placeholder, lets driver determine MCS rate 255 mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); 256 } 257 initExperiments()258 private void initExperiments() { 259 for (int delay : EXPERIMENT_DELAYS_MS) { 260 for (int rssiThreshold : EXPERIMENT_RSSIS) { 261 for (int linkSpeedThreshold: EXPERIMENT_LINK_SPEEDS) { 262 Experiment experiment = new Experiment(mClock, mWifiMetrics, 263 delay, delay, delay, rssiThreshold, linkSpeedThreshold); 264 mExperiments.add(experiment); 265 } 266 } 267 } 268 } 269 270 // TODO(b/131091030): remove once experiment is over 271 private static class Experiment { 272 273 private final Clock mClock; 274 private final WifiMetrics mWifiMetrics; 275 private final int mScreenOnDelayMs; 276 private final int mNoTxDelayMs; 277 private final int mDelayBetweenProbesMs; 278 private final int mRssiThreshold; 279 private final int mLinkSpeedThreshold; 280 private final String mExperimentId; 281 282 private long mLastLinkProbeTimestampMs; 283 private long mLastTxSuccessIncreaseTimestampMs; 284 private long mLastTxSuccessCount; 285 private long mLastScreenOnTimestampMs; 286 Experiment(Clock clock, WifiMetrics wifiMetrics, int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs, int rssiThreshold, int linkSpeedThreshold)287 Experiment(Clock clock, WifiMetrics wifiMetrics, 288 int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs, 289 int rssiThreshold, int linkSpeedThreshold) { 290 mClock = clock; 291 mWifiMetrics = wifiMetrics; 292 mScreenOnDelayMs = screenOnDelayMs; 293 mNoTxDelayMs = noTxDelayMs; 294 mDelayBetweenProbesMs = delayBetweenProbesMs; 295 mRssiThreshold = rssiThreshold; 296 mLinkSpeedThreshold = linkSpeedThreshold; 297 298 mExperimentId = getExperimentId(); 299 300 resetOnNewConnection(); 301 resetOnScreenTurnedOn(); 302 } 303 getExperimentId()304 private String getExperimentId() { 305 return "[screenOnDelay=" + mScreenOnDelayMs + ',' 306 + "noTxDelay=" + mNoTxDelayMs + ',' 307 + "delayBetweenProbes=" + mDelayBetweenProbesMs + ',' 308 + "rssiThreshold=" + mRssiThreshold + ',' 309 + "linkSpeedThreshold=" + mLinkSpeedThreshold + ']'; 310 } 311 resetOnNewConnection()312 void resetOnNewConnection() { 313 long now = mClock.getElapsedSinceBootMillis(); 314 mLastLinkProbeTimestampMs = now; 315 mLastTxSuccessIncreaseTimestampMs = now; 316 mLastTxSuccessCount = 0; 317 } 318 resetOnScreenTurnedOn()319 void resetOnScreenTurnedOn() { 320 mLastScreenOnTimestampMs = mClock.getElapsedSinceBootMillis(); 321 } 322 updateConnectionStats(WifiInfo wifiInfo)323 void updateConnectionStats(WifiInfo wifiInfo) { 324 long now = mClock.getElapsedSinceBootMillis(); 325 326 if (mLastTxSuccessCount < wifiInfo.txSuccess) { 327 mLastTxSuccessIncreaseTimestampMs = now; 328 } 329 mLastTxSuccessCount = wifiInfo.txSuccess; 330 331 long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; 332 if (timeSinceLastLinkProbeMs < mDelayBetweenProbesMs) { 333 return; 334 } 335 336 // if tx succeeded at least once in the last LINK_PROBE_INTERVAL_MS, don't need to probe 337 long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; 338 if (timeSinceLastTxSuccessIncreaseMs < mNoTxDelayMs) { 339 return; 340 } 341 342 long timeSinceLastScreenOnMs = now - mLastScreenOnTimestampMs; 343 if (timeSinceLastScreenOnMs < SCREEN_ON_DELAY_MS) { 344 return; 345 } 346 347 // can skip probing if RSSI is valid and high and link speed is fast 348 int rssi = wifiInfo.getRssi(); 349 int linkSpeed = wifiInfo.getLinkSpeed(); 350 if (rssi != WifiInfo.INVALID_RSSI && rssi > mRssiThreshold 351 && linkSpeed > mLinkSpeedThreshold) { 352 return; 353 } 354 355 mWifiMetrics.incrementLinkProbeExperimentProbeCount(mExperimentId); 356 357 mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); 358 } 359 } 360 } 361