1 /*
2  * Copyright (C) 2023 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.annotation.NonNull;
20 import android.net.wifi.WifiInfo;
21 import android.net.wifi.WifiManager;
22 import android.net.wifi.WifiManager.DeviceMobilityState;
23 import android.util.Log;
24 
25 import java.util.Arrays;
26 
27 /**
28  * Class for App and client mode RSSI monitoring. It processes the RSSI thresholds for these
29  * monitors and enables/disables the monitoring accordingly. It also changes the RSSI polling
30  * interval dynamically based on the client mode RSSI monitoring and device mobility state.
31  */
32 public class RssiMonitor {
33     private static final String TAG = "RssiMonitor";
34     private boolean mVerboseLoggingEnabled = false;
35 
36     private final WifiGlobals mWifiGlobals;
37     private final WifiThreadRunner mWifiThreadRunner;
38     private final WifiInfo mWifiInfo;
39     private final WifiNative mWifiNative;
40     private final String mInterfaceName;
41     private final Runnable mUpdateCapabilityRunnable;
42     private final DeviceConfigFacade mDeviceConfigFacade;
43 
44     private boolean mEnableClientRssiMonitor = false;
45     private boolean mIsPollRssiIntervalOverridden = false;
46     private int[] mAppThresholds = {};
47     private byte[] mRssiRanges = {};
48 
RssiMonitor(WifiGlobals wifiGlobals, WifiThreadRunner wifiThreadRunner, WifiInfo wifiInfo, WifiNative wifiNative, String interfaceName, Runnable updateCapabilityRunnable, DeviceConfigFacade deviceConfigFacade)49     public RssiMonitor(WifiGlobals wifiGlobals, WifiThreadRunner wifiThreadRunner,
50             WifiInfo wifiInfo, WifiNative wifiNative, String interfaceName,
51             Runnable updateCapabilityRunnable, DeviceConfigFacade deviceConfigFacade) {
52         mWifiGlobals = wifiGlobals;
53         mWifiThreadRunner = wifiThreadRunner;
54         mWifiInfo = wifiInfo;
55         mWifiNative = wifiNative;
56         mInterfaceName = interfaceName;
57         mUpdateCapabilityRunnable = updateCapabilityRunnable;
58         mDeviceConfigFacade = deviceConfigFacade;
59     }
60 
logd(String string)61     private void logd(String string) {
62         if (mVerboseLoggingEnabled) {
63             Log.d(getTag(), string);
64         }
65     }
66 
getTag()67     private String getTag() {
68         return TAG + "[" + (mInterfaceName == null ? "unknown" : mInterfaceName) + "]";
69     }
70 
71     class RssiEventHandler implements WifiNative.WifiRssiEventHandler {
72         @Override
onRssiThresholdBreached(byte curRssi)73         public void onRssiThresholdBreached(byte curRssi) {
74             if (mVerboseLoggingEnabled) {
75                 logd("onRssiThresholdBreach event. Cur Rssi = " + curRssi);
76             }
77             // Corner case: if framework threshold is same as one of the app thresholds,
78             // processClientRssiThresholdBreached(curRssi) is called. The framework monitor will
79             // be disabled, and the RSSI monitor will be restarted with the app thresholds
80             // by calling handleRssiBreachRestartRssiMonitor(). From the App monitor perspective
81             // the actions are same as calling handleRssiBreachRestartRssiMonitor(curRssi) directly.
82             if (mEnableClientRssiMonitor && curRssi
83                     <= mWifiGlobals.getClientRssiMonitorThresholdDbm()) {
84                 mWifiThreadRunner.post(() -> processClientRssiThresholdBreached(curRssi),
85                         TAG + "#processClientRssiThresholdBreached");
86             } else {
87                 mWifiThreadRunner.post(() -> handleRssiBreachRestartRssiMonitor(curRssi),
88                         TAG + "#handleRssiBreachRestartRssiMonitor");
89             }
90         }
91     }
92     private final RssiEventHandler mRssiEventHandler = new RssiEventHandler();
93 
processClientRssiThresholdBreached(int curRssi)94     private void processClientRssiThresholdBreached(int curRssi) {
95         int shortInterval = mWifiGlobals.getPollRssiShortIntervalMillis();
96         logd("Client mode RSSI monitor threshold breach event. RSSI polling interval changed to "
97                 + shortInterval + " ms" + ", disable client mode RSSI monitor");
98         mWifiGlobals.setPollRssiIntervalMillis(shortInterval);
99         disableClientRssiMonitorAndUpdateThresholds(curRssi);
100     }
101 
handleRssiBreachRestartRssiMonitor(byte curRssi)102     private void handleRssiBreachRestartRssiMonitor(byte curRssi) {
103         if (curRssi == Byte.MAX_VALUE || curRssi == Byte.MIN_VALUE) {
104             Log.wtf(getTag(), "Process RSSI thresholds: Invalid rssi " + curRssi);
105             return;
106         }
107         for (int i = 1; i < mRssiRanges.length; i++) {
108             if (curRssi < mRssiRanges[i]) {
109                 // Assume sorted values(ascending order) for rssi,
110                 // bounded by high(127) and low(-128) at extremeties
111                 byte maxRssi = mRssiRanges[i];
112                 byte minRssi = mRssiRanges[i - 1];
113                 // This value of hw has to be believed as this value is averaged and has breached
114                 // the rssi thresholds and raised event to host. This would be eggregious if this
115                 // value is invalid
116                 mWifiInfo.setRssi(curRssi);
117                 mUpdateCapabilityRunnable.run();
118                 int ret = startRssiMonitoringOffload(maxRssi, minRssi);
119                 logd("Re-program RSSI thresholds " + ": [" + minRssi + ", "
120                         + maxRssi + "], curRssi=" + curRssi + " ret=" + ret);
121                 break;
122             }
123         }
124     }
125 
startRssiMonitoringOffload(byte maxRssi, byte minRssi)126     private int startRssiMonitoringOffload(byte maxRssi, byte minRssi) {
127         return mWifiNative.startRssiMonitoring(mInterfaceName, maxRssi, minRssi, mRssiEventHandler);
128     }
129 
stopRssiMonitoringOffload()130     private int stopRssiMonitoringOffload() {
131         return mWifiNative.stopRssiMonitoring(mInterfaceName);
132     }
133 
134     /**
135      * Reset the RSSI monitor
136      */
reset()137     public void reset() {
138         mEnableClientRssiMonitor = false;
139         mAppThresholds = new int[] {};
140         mRssiRanges = new byte[] {};
141         if (!mIsPollRssiIntervalOverridden) {
142             int shortInterval = mWifiGlobals.getPollRssiShortIntervalMillis();
143             mWifiGlobals.setPollRssiIntervalMillis(shortInterval);
144         }
145         stopRssiMonitoringOffload();
146     }
147 
148     /**
149      * Enable/Disable verbose logging.
150      * @param verbose true to enable and false to disable.
151      */
enableVerboseLogging(boolean verbose)152     public void enableVerboseLogging(boolean verbose) {
153         mVerboseLoggingEnabled = verbose;
154     }
155 
156     /**
157      * Update the RSSI polling interval based on the current device mobility state and RSSI.
158      * If the device is stationary and RSSI is high, change to the long interval. Otherwise,
159      * change to the short interval.
160      * @param state the current device mobility state
161      */
updatePollRssiInterval(@eviceMobilityState int state)162     public void updatePollRssiInterval(@DeviceMobilityState int state) {
163         if (!mWifiGlobals.isAdjustPollRssiIntervalEnabled()
164                 || !mDeviceConfigFacade.isAdjustPollRssiIntervalEnabled()
165                 || mIsPollRssiIntervalOverridden) {
166             return;
167         }
168         int curRssi = mWifiInfo.getRssi();
169         int rssiMonitorThreshold = mWifiGlobals.getClientRssiMonitorThresholdDbm();
170         int rssiMonitorHysteresisDb = mWifiGlobals.getClientRssiMonitorHysteresisDb();
171         if (state == WifiManager.DEVICE_MOBILITY_STATE_STATIONARY && curRssi
172                 >= rssiMonitorThreshold + rssiMonitorHysteresisDb) {
173             setLongPollRssiInterval();
174         } else if (state != WifiManager.DEVICE_MOBILITY_STATE_STATIONARY || curRssi
175                 < rssiMonitorThreshold) {
176             setShortPollRssiInterval();
177         }
178     }
179 
setLongPollRssiInterval()180     private void setLongPollRssiInterval() {
181         int longInterval = mWifiGlobals.getPollRssiLongIntervalMillis();
182         if (mWifiGlobals.getPollRssiIntervalMillis() == longInterval) {
183             return;
184         }
185         logd("RSSI polling interval changed to " + longInterval + " ms"
186                 + ", enable client mode RSSI monitor");
187         mWifiGlobals.setPollRssiIntervalMillis(longInterval);
188         enableClientRssiMonitorAndUpdateThresholds(mWifiInfo.getRssi());
189     }
190 
191     /**
192      * Change the RSSI polling interval to the short interval and disable client mode RSSI monitor
193      */
setShortPollRssiInterval()194     public void setShortPollRssiInterval() {
195         if (mIsPollRssiIntervalOverridden) {
196             return;
197         }
198         int shortInterval = mWifiGlobals.getPollRssiShortIntervalMillis();
199         if (mWifiGlobals.getPollRssiIntervalMillis() == shortInterval) {
200             return;
201         }
202         logd("RSSI polling interval changed to " + shortInterval + " ms"
203                 + ", disable client mode RSSI monitor");
204         mWifiGlobals.setPollRssiIntervalMillis(shortInterval);
205         disableClientRssiMonitorAndUpdateThresholds(mWifiInfo.getRssi());
206     }
207 
enableClientRssiMonitorAndUpdateThresholds(int curRssi)208     private void enableClientRssiMonitorAndUpdateThresholds(int curRssi) {
209         mEnableClientRssiMonitor = true;
210         updateRssiRangesAndStartMonitor(curRssi);
211     }
212 
disableClientRssiMonitorAndUpdateThresholds(int curRssi)213     private void disableClientRssiMonitorAndUpdateThresholds(int curRssi) {
214         mEnableClientRssiMonitor = false;
215         updateRssiRangesAndStartMonitor(curRssi);
216     }
217 
218     /**
219      * Pass the updated App RSSI thresholds to RssiMonitor and start/restart RSSI monitoring if
220      * the thresholds are valid after processing.
221      */
updateAppThresholdsAndStartMonitor(@onNull int[] appThresholds)222     public void updateAppThresholdsAndStartMonitor(@NonNull int[] appThresholds) {
223         logd("Received app signal strength thresholds: " + Arrays.toString(appThresholds));
224         mAppThresholds = appThresholds;
225         updateRssiRangesAndStartMonitor(mWifiInfo.getRssi());
226     }
227 
updateRssiRangesAndStartMonitor(int curRssi)228     private void updateRssiRangesAndStartMonitor(int curRssi) {
229         // 0. If there are no thresholds, or if the thresholds are invalid,
230         //    stop RSSI monitoring.
231         // 1. Tell the hardware to start RSSI monitoring here, possibly adding MIN_VALUE and
232         //    MAX_VALUE at the start/end of the thresholds array if necessary.
233         // 2. Ensure that when the hardware event fires, we fetch the RSSI from the hardware
234         //    event, call mWifiInfo.setRssi() with it, and call updateCapabilities(), and then
235         //    re-arm the hardware event. This needs to be done on the state machine thread to
236         //    avoid race conditions. The RSSI used to re-arm the event (and perhaps also the one
237         //    sent in the NetworkCapabilities) must be the one received from the hardware event
238         //    received, or we might skip callbacks.
239         // 3. Ensure that when we disconnect, RSSI monitoring is stopped.
240         int[] mergedThresholds = mergeAppFrameworkThresholds(mAppThresholds);
241         if (mergedThresholds.length == 0) {
242             mRssiRanges = new byte[] {};
243             stopRssiMonitoringOffload();
244             return;
245         }
246         int [] rssiVals = Arrays.copyOf(mergedThresholds, mergedThresholds.length + 2);
247         rssiVals[rssiVals.length - 2] = Byte.MIN_VALUE;
248         rssiVals[rssiVals.length - 1] = Byte.MAX_VALUE;
249         Arrays.sort(rssiVals);
250         byte[] rssiRange = new byte[rssiVals.length];
251         for (int i = 0; i < rssiVals.length; i++) {
252             int val = rssiVals[i];
253             if (val <= Byte.MAX_VALUE && val >= Byte.MIN_VALUE) {
254                 rssiRange[i] = (byte) val;
255             } else {
256                 Log.e(getTag(), "Illegal value " + val + " for RSSI thresholds: "
257                         + Arrays.toString(rssiVals));
258                 stopRssiMonitoringOffload();
259                 return;
260             }
261         }
262         mRssiRanges = rssiRange;
263         logd("Updated RSSI thresholds=" + Arrays.toString(mRssiRanges));
264         handleRssiBreachRestartRssiMonitor((byte) curRssi);
265     }
266 
mergeAppFrameworkThresholds(@onNull int[] appThresholds)267     private int[] mergeAppFrameworkThresholds(@NonNull int[] appThresholds) {
268         if (mEnableClientRssiMonitor) {
269             int[] mergedThresholds = Arrays.copyOf(appThresholds,
270                     appThresholds.length + 1);
271             mergedThresholds[mergedThresholds.length - 1] = mWifiGlobals
272                     .getClientRssiMonitorThresholdDbm();
273             return mergedThresholds;
274         } else {
275             return appThresholds;
276         }
277     }
278 
279     /**
280      * When link layer stats polling interval is overridden, set the fixed interval and disable
281      * client mode RSSI monitoring if it is enabled. When the polling interval is set to automatic
282      * handling, set the interval to the regular (short) interval, then the RSSI monitor will
283      * adjust the interval automatically
284      * @param newIntervalMs a non-negative integer, for the link layer stats polling interval
285      *                      in milliseconds.
286      *                      To set a fixed interval, use a positive value.
287      *                      For automatic handling of the interval, use value 0
288      */
overridePollRssiInterval(int newIntervalMs)289     public void overridePollRssiInterval(int newIntervalMs) {
290         if (mIsPollRssiIntervalOverridden && newIntervalMs == 0) {
291             setAutoPollRssiInterval();
292             return;
293         }
294         if (newIntervalMs > 0) {
295             setFixedPollRssiInterval(newIntervalMs);
296         }
297     }
298 
setAutoPollRssiInterval()299     private void setAutoPollRssiInterval() {
300         mIsPollRssiIntervalOverridden = false;
301         int regularInterval = mWifiGlobals.getPollRssiShortIntervalMillis();
302         mWifiGlobals.setPollRssiIntervalMillis(regularInterval);
303     }
304 
setFixedPollRssiInterval(int newIntervalMs)305     private void setFixedPollRssiInterval(int newIntervalMs) {
306         mIsPollRssiIntervalOverridden = true;
307         mWifiGlobals.setPollRssiIntervalMillis(newIntervalMs);
308         if (mEnableClientRssiMonitor) {
309             disableClientRssiMonitorAndUpdateThresholds(mWifiInfo.getRssi());
310         }
311     }
312 }
313