blob: 863268811db1914e06a74ae160d3c5ced54e6665 [file] [log] [blame]
/*
* 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(
timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed,
elapsedTimeMs);
}
@Override
public void onFailure(int reason) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "link probing failure, reason=" + reason);
}
mWifiMetrics.logLinkProbeFailure(
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();
}
}
}