| /* |
| * Copyright (C) 2019 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.MacAddress; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.nl80211.WifiNl80211Manager; |
| import android.os.Handler; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.wifi.util.TimedQuotaManager; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.time.Duration; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Tracks state that decides if a link probe should be performed. If so, trigger a link probe to |
| * evaluate connection quality. |
| */ |
| public class LinkProbeManager { |
| private static final String TAG = "WifiLinkProbeManager"; |
| |
| private static final int WIFI_LINK_PROBING_ENABLED_DEFAULT = 1; // 1 = enabled |
| |
| // TODO(112029045): Use constants from ScoringParams instead |
| @VisibleForTesting static final int RSSI_THRESHOLD = -70; |
| @VisibleForTesting static final int LINK_SPEED_THRESHOLD_MBPS = 15; // in megabits per second |
| /** Minimum delay before probing after the last probe. */ |
| @VisibleForTesting static final long DELAY_BETWEEN_PROBES_MS = 6000; |
| /** Minimum delay before probing after screen turned on. */ |
| @VisibleForTesting static final long SCREEN_ON_DELAY_MS = 6000; |
| /** |
| * Minimum delay before probing after last increase of the Tx success counter (which indicates |
| * that a data frame (i.e. not counting management frame) was successfully transmitted). |
| */ |
| @VisibleForTesting static final long DELAY_AFTER_TX_SUCCESS_MS = 6000; |
| |
| @VisibleForTesting static final long MAX_PROBE_COUNT_IN_PERIOD = |
| WifiMetrics.MAX_LINK_PROBE_STA_EVENTS; |
| @VisibleForTesting static final long PERIOD_MILLIS = Duration.ofDays(1).toMillis(); |
| |
| @VisibleForTesting static final int[] EXPERIMENT_DELAYS_MS = {3000, 6000, 9000, 12000, 15000}; |
| @VisibleForTesting static final int[] EXPERIMENT_RSSIS = {-65, -70, -75}; |
| @VisibleForTesting static final int[] EXPERIMENT_LINK_SPEEDS = {10, 15, 20}; |
| private List<Experiment> mExperiments = new ArrayList<>(); |
| |
| private final Clock mClock; |
| private final WifiNative mWifiNative; |
| private final WifiMetrics mWifiMetrics; |
| private final FrameworkFacade mFrameworkFacade; |
| private final Handler mHandler; |
| private final Context mContext; |
| |
| private Boolean mLinkProbingSupported = null; |
| |
| private boolean mVerboseLoggingEnabled = false; |
| |
| /** |
| * Tracks the last timestamp when a link probe was triggered. Link probing only occurs when at |
| * least {@link #DELAY_BETWEEN_PROBES_MS} has passed since the last link probe. |
| */ |
| private long mLastLinkProbeTimestampMs; |
| /** |
| * Tracks the last timestamp when {@link WifiInfo#txSuccess} was increased i.e. the last time a |
| * Tx was successful. Link probing only occurs when at least {@link #DELAY_AFTER_TX_SUCCESS_MS} |
| * has passed since the last Tx success. |
| * This is also reset to the current time when {@link #resetOnNewConnection()} is called, so |
| * that a link probe only occurs at least {@link #DELAY_AFTER_TX_SUCCESS_MS} after a new |
| * connection is made. |
| */ |
| private long mLastTxSuccessIncreaseTimestampMs; |
| /** |
| * Stores the last value of {@link WifiInfo#txSuccess}. The current value of |
| * {@link WifiInfo#txSuccess} is compared against the last value to determine whether there was |
| * a successful Tx. |
| */ |
| private long mLastTxSuccessCount; |
| /** |
| * Tracks the last timestamp when the screen turned on. Link probing only occurs when at least |
| * {@link #SCREEN_ON_DELAY_MS} has passed since the last time the screen was turned on. |
| */ |
| private long mLastScreenOnTimestampMs; |
| private final TimedQuotaManager mTimedQuotaManager; |
| |
| public LinkProbeManager(Clock clock, WifiNative wifiNative, WifiMetrics wifiMetrics, |
| FrameworkFacade frameworkFacade, Handler handler, Context context) { |
| mClock = clock; |
| mWifiNative = wifiNative; |
| mWifiMetrics = wifiMetrics; |
| mFrameworkFacade = frameworkFacade; |
| mHandler = handler; |
| mContext = context; |
| mTimedQuotaManager = new TimedQuotaManager(clock, MAX_PROBE_COUNT_IN_PERIOD, PERIOD_MILLIS); |
| |
| initExperiments(); |
| } |
| |
| private boolean isLinkProbingSupported() { |
| if (mLinkProbingSupported == null) { |
| mLinkProbingSupported = mContext.getResources() |
| .getBoolean(R.bool.config_wifi_link_probing_supported); |
| if (mLinkProbingSupported) { |
| resetOnNewConnection(); |
| resetOnScreenTurnedOn(); |
| } |
| } |
| return mLinkProbingSupported; |
| } |
| |
| /** enables/disables wifi verbose logging */ |
| public void enableVerboseLogging(boolean enable) { |
| mVerboseLoggingEnabled = enable; |
| } |
| |
| /** dumps internal state */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of LinkProbeManager"); |
| pw.println("LinkProbeManager - link probing supported by device: " |
| + isLinkProbingSupported()); |
| pw.println("LinkProbeManager - mLastLinkProbeTimestampMs: " + mLastLinkProbeTimestampMs); |
| pw.println("LinkProbeManager - mLastTxSuccessIncreaseTimestampMs: " |
| + mLastTxSuccessIncreaseTimestampMs); |
| pw.println("LinkProbeManager - mLastTxSuccessCount: " + mLastTxSuccessCount); |
| pw.println("LinkProbeManager - mLastScreenOnTimestampMs: " + mLastScreenOnTimestampMs); |
| pw.println("LinkProbeManager - mTimedQuotaManager: " + mTimedQuotaManager); |
| } |
| |
| /** |
| * When connecting to a new network, reset internal state. |
| */ |
| public void resetOnNewConnection() { |
| mExperiments.forEach(Experiment::resetOnNewConnection); |
| if (!isLinkProbingSupported()) return; |
| |
| long now = mClock.getElapsedSinceBootMillis(); |
| mLastLinkProbeTimestampMs = now; |
| mLastTxSuccessIncreaseTimestampMs = now; |
| mLastTxSuccessCount = 0; |
| } |
| |
| /** |
| * When RSSI poll events are stopped and restarted (usually screen turned off then back on), |
| * reset internal state. |
| */ |
| public void resetOnScreenTurnedOn() { |
| mExperiments.forEach(Experiment::resetOnScreenTurnedOn); |
| if (!isLinkProbingSupported()) return; |
| |
| mLastScreenOnTimestampMs = mClock.getElapsedSinceBootMillis(); |
| } |
| |
| /** |
| * Based on network conditions provided by WifiInfo, decides if a link probe should be |
| * performed. If so, trigger a link probe and report the results to WifiMetrics. |
| * |
| * @param wifiInfo the updated WifiInfo |
| * @param interfaceName the interface that the link probe should be performed on, if applicable. |
| */ |
| public void updateConnectionStats(WifiInfo wifiInfo, String interfaceName) { |
| mExperiments.forEach(e -> e.updateConnectionStats(wifiInfo)); |
| |
| if (!isLinkProbingSupported()) return; |
| |
| long now = mClock.getElapsedSinceBootMillis(); |
| |
| // at least 1 tx succeeded since last update |
| if (mLastTxSuccessCount < wifiInfo.txSuccess) { |
| mLastTxSuccessIncreaseTimestampMs = now; |
| } |
| mLastTxSuccessCount = wifiInfo.txSuccess; |
| |
| // maximum 1 link probe every DELAY_BETWEEN_PROBES_MS |
| long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; |
| if (timeSinceLastLinkProbeMs < DELAY_BETWEEN_PROBES_MS) { |
| return; |
| } |
| |
| // if tx succeeded at least once in the last DELAY_AFTER_TX_SUCCESS_MS, don't need to probe |
| long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; |
| if (timeSinceLastTxSuccessIncreaseMs < DELAY_AFTER_TX_SUCCESS_MS) { |
| return; |
| } |
| |
| // if not enough time has passed since the screen last turned on, don't probe |
| long timeSinceLastScreenOnMs = now - mLastScreenOnTimestampMs; |
| if (timeSinceLastScreenOnMs < SCREEN_ON_DELAY_MS) { |
| return; |
| } |
| |
| // can skip probing if RSSI is valid and high and link speed is fast |
| int rssi = wifiInfo.getRssi(); |
| int linkSpeed = wifiInfo.getLinkSpeed(); |
| if (rssi != WifiInfo.INVALID_RSSI && rssi > RSSI_THRESHOLD |
| && linkSpeed > LINK_SPEED_THRESHOLD_MBPS) { |
| return; |
| } |
| |
| if (!mTimedQuotaManager.requestQuota()) { |
| return; |
| } |
| |
| if (mVerboseLoggingEnabled) { |
| Log.d(TAG, String.format( |
| "link probing triggered with conditions: timeSinceLastLinkProbeMs=%d " |
| + "timeSinceLastTxSuccessIncreaseMs=%d rssi=%d linkSpeed=%s", |
| timeSinceLastLinkProbeMs, timeSinceLastTxSuccessIncreaseMs, |
| rssi, linkSpeed)); |
| } |
| |
| // TODO(b/112029045): also report MCS rate to metrics when supported by driver |
| mWifiNative.probeLink( |
| interfaceName, |
| MacAddress.fromString(wifiInfo.getBSSID()), |
| new WifiNl80211Manager.SendMgmtFrameCallback() { |
| @Override |
| public void onAck(int elapsedTimeMs) { |
| if (mVerboseLoggingEnabled) { |
| Log.d(TAG, "link probing success, elapsedTimeMs=" |
| + elapsedTimeMs); |
| } |
| mWifiMetrics.logLinkProbeSuccess(interfaceName, |
| timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, elapsedTimeMs); |
| } |
| |
| @Override |
| public void onFailure(int reason) { |
| if (mVerboseLoggingEnabled) { |
| Log.d(TAG, "link probing failure, reason=" + reason); |
| } |
| mWifiMetrics.logLinkProbeFailure(interfaceName, |
| timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, reason); |
| } |
| }, |
| -1); // placeholder, lets driver determine MCS rate |
| mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); |
| } |
| |
| private void initExperiments() { |
| for (int delay : EXPERIMENT_DELAYS_MS) { |
| for (int rssiThreshold : EXPERIMENT_RSSIS) { |
| for (int linkSpeedThreshold: EXPERIMENT_LINK_SPEEDS) { |
| Experiment experiment = new Experiment(mClock, mWifiMetrics, |
| delay, delay, delay, rssiThreshold, linkSpeedThreshold); |
| mExperiments.add(experiment); |
| } |
| } |
| } |
| } |
| |
| // TODO(b/131091030): remove once experiment is over |
| private static class Experiment { |
| |
| private final Clock mClock; |
| private final WifiMetrics mWifiMetrics; |
| private final int mScreenOnDelayMs; |
| private final int mNoTxDelayMs; |
| private final int mDelayBetweenProbesMs; |
| private final int mRssiThreshold; |
| private final int mLinkSpeedThreshold; |
| private final String mExperimentId; |
| |
| private long mLastLinkProbeTimestampMs; |
| private long mLastTxSuccessIncreaseTimestampMs; |
| private long mLastTxSuccessCount; |
| private long mLastScreenOnTimestampMs; |
| |
| Experiment(Clock clock, WifiMetrics wifiMetrics, |
| int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs, |
| int rssiThreshold, int linkSpeedThreshold) { |
| mClock = clock; |
| mWifiMetrics = wifiMetrics; |
| mScreenOnDelayMs = screenOnDelayMs; |
| mNoTxDelayMs = noTxDelayMs; |
| mDelayBetweenProbesMs = delayBetweenProbesMs; |
| mRssiThreshold = rssiThreshold; |
| mLinkSpeedThreshold = linkSpeedThreshold; |
| |
| mExperimentId = getExperimentId(); |
| |
| resetOnNewConnection(); |
| resetOnScreenTurnedOn(); |
| } |
| |
| private String getExperimentId() { |
| return "[screenOnDelay=" + mScreenOnDelayMs + ',' |
| + "noTxDelay=" + mNoTxDelayMs + ',' |
| + "delayBetweenProbes=" + mDelayBetweenProbesMs + ',' |
| + "rssiThreshold=" + mRssiThreshold + ',' |
| + "linkSpeedThreshold=" + mLinkSpeedThreshold + ']'; |
| } |
| |
| void resetOnNewConnection() { |
| long now = mClock.getElapsedSinceBootMillis(); |
| mLastLinkProbeTimestampMs = now; |
| mLastTxSuccessIncreaseTimestampMs = now; |
| mLastTxSuccessCount = 0; |
| } |
| |
| void resetOnScreenTurnedOn() { |
| mLastScreenOnTimestampMs = mClock.getElapsedSinceBootMillis(); |
| } |
| |
| void updateConnectionStats(WifiInfo wifiInfo) { |
| long now = mClock.getElapsedSinceBootMillis(); |
| |
| if (mLastTxSuccessCount < wifiInfo.txSuccess) { |
| mLastTxSuccessIncreaseTimestampMs = now; |
| } |
| mLastTxSuccessCount = wifiInfo.txSuccess; |
| |
| long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; |
| if (timeSinceLastLinkProbeMs < mDelayBetweenProbesMs) { |
| return; |
| } |
| |
| // if tx succeeded at least once in the last LINK_PROBE_INTERVAL_MS, don't need to probe |
| long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; |
| if (timeSinceLastTxSuccessIncreaseMs < mNoTxDelayMs) { |
| return; |
| } |
| |
| long timeSinceLastScreenOnMs = now - mLastScreenOnTimestampMs; |
| if (timeSinceLastScreenOnMs < SCREEN_ON_DELAY_MS) { |
| return; |
| } |
| |
| // can skip probing if RSSI is valid and high and link speed is fast |
| int rssi = wifiInfo.getRssi(); |
| int linkSpeed = wifiInfo.getLinkSpeed(); |
| if (rssi != WifiInfo.INVALID_RSSI && rssi > mRssiThreshold |
| && linkSpeed > mLinkSpeedThreshold) { |
| return; |
| } |
| |
| mWifiMetrics.incrementLinkProbeExperimentProbeCount(mExperimentId); |
| |
| mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); |
| } |
| } |
| } |