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