• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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