| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2011 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 android.net.wifi; |
| 18 | |
| 19 | import android.content.BroadcastReceiver; |
| 20 | import android.content.ContentResolver; |
| 21 | import android.content.Context; |
| 22 | import android.content.Intent; |
| 23 | import android.content.IntentFilter; |
| 24 | import android.database.ContentObserver; |
| 25 | import android.net.ConnectivityManager; |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 26 | import android.net.LinkProperties; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 27 | import android.net.NetworkInfo; |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 28 | import android.net.wifi.RssiPacketCountInfo; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 29 | import android.os.Message; |
| 30 | import android.os.SystemClock; |
| 31 | import android.provider.Settings; |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 32 | import android.provider.Settings.Secure; |
| Robert Greenwalt | 9b2886e | 2011-08-31 11:46:42 -0700 | [diff] [blame] | 33 | import android.util.Log; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 34 | import android.util.LruCache; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 35 | |
| Irfan Sheriff | 6b66e9e | 2011-10-24 16:55:50 -0700 | [diff] [blame] | 36 | import com.android.internal.R; |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 37 | import com.android.internal.util.AsyncChannel; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 38 | import com.android.internal.util.Protocol; |
| 39 | import com.android.internal.util.State; |
| 40 | import com.android.internal.util.StateMachine; |
| 41 | |
| Irfan Sheriff | f0afe41 | 2012-11-30 14:07:44 -0800 | [diff] [blame] | 42 | import java.io.FileDescriptor; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 43 | import java.io.PrintWriter; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 44 | import java.text.DecimalFormat; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 45 | |
| 46 | /** |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 47 | * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi |
| 48 | * connects at L2 layer, the beacons from access point reach the device and it |
| 49 | * can maintain a connection, but the application connectivity can be flaky (due |
| 50 | * to bigger packet size exchange). |
| 51 | * <p> |
| 52 | * We now monitor the quality of the last hop on WiFi using packet loss ratio as |
| 53 | * an indicator to decide if the link is good enough to switch to Wi-Fi as the |
| 54 | * uplink. |
| 55 | * <p> |
| 56 | * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the |
| 57 | * instant packet loss, and record it as per-AP loss-to-rssi statistics. When |
| 58 | * the instant packet loss is higher than a threshold, the WiFi watchdog sends a |
| 59 | * poor link notification to avoid WiFi connection temporarily. |
| 60 | * <p> |
| 61 | * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to |
| 62 | * bring the WiFi connection back. Once the RSSI is high enough to achieve a |
| 63 | * lower packet loss, a good link detection is sent such that the WiFi |
| 64 | * connection become available again. |
| 65 | * <p> |
| 66 | * BSSID roaming has been taken into account. When user is moving across |
| 67 | * multiple APs, the WiFi watchdog will detect that and keep watching the |
| 68 | * currently connected AP. |
| 69 | * <p> |
| 70 | * Power impact should be minimal since much of the measurement relies on |
| 71 | * passive statistics already being tracked at the driver and the polling is |
| 72 | * done when screen is turned on and the RSSI is in a certain range. |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 73 | * |
| 74 | * @hide |
| 75 | */ |
| 76 | public class WifiWatchdogStateMachine extends StateMachine { |
| 77 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 78 | /* STOPSHIP: Keep this configurable for debugging until ship */ |
| 79 | private static boolean DBG = false; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 80 | |
| 81 | private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; |
| 82 | |
| 83 | /* Internal events */ |
| 84 | private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; |
| 85 | private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; |
| 86 | private static final int EVENT_RSSI_CHANGE = BASE + 3; |
| 87 | private static final int EVENT_SUPPLICANT_STATE_CHANGE = BASE + 4; |
| 88 | private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; |
| 89 | private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; |
| 90 | private static final int EVENT_BSSID_CHANGE = BASE + 7; |
| 91 | private static final int EVENT_SCREEN_ON = BASE + 8; |
| 92 | private static final int EVENT_SCREEN_OFF = BASE + 9; |
| 93 | |
| 94 | /* Internal messages */ |
| Irfan Sheriff | da6da09 | 2012-08-16 12:49:23 -0700 | [diff] [blame] | 95 | private static final int CMD_RSSI_FETCH = BASE + 11; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 96 | |
| 97 | /* Notifications from/to WifiStateMachine */ |
| 98 | static final int POOR_LINK_DETECTED = BASE + 21; |
| 99 | static final int GOOD_LINK_DETECTED = BASE + 22; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 100 | |
| Irfan Sheriff | 17cf1f2 | 2012-10-09 17:08:39 -0700 | [diff] [blame] | 101 | public static final boolean DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED = false; |
| 102 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 103 | /* |
| 104 | * RSSI levels as used by notification icon |
| 105 | * Level 4 -55 <= RSSI |
| 106 | * Level 3 -66 <= RSSI < -55 |
| 107 | * Level 2 -77 <= RSSI < -67 |
| 108 | * Level 1 -88 <= RSSI < -78 |
| 109 | * Level 0 RSSI < -88 |
| 110 | */ |
| 111 | |
| 112 | /** |
| 113 | * WiFi link statistics is monitored and recorded actively below this threshold. |
| 114 | * <p> |
| 115 | * Larger threshold is more adaptive but increases sampling cost. |
| 116 | */ |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 117 | private static final int LINK_MONITOR_LEVEL_THRESHOLD = WifiManager.RSSI_LEVELS - 1; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 118 | |
| 119 | /** |
| 120 | * Remember packet loss statistics of how many BSSIDs. |
| 121 | * <p> |
| 122 | * Larger size is usually better but requires more space. |
| 123 | */ |
| 124 | private static final int BSSID_STAT_CACHE_SIZE = 20; |
| 125 | |
| 126 | /** |
| 127 | * RSSI range of a BSSID statistics. |
| 128 | * Within the range, (RSSI -> packet loss %) mappings are stored. |
| 129 | * <p> |
| 130 | * Larger range is usually better but requires more space. |
| 131 | */ |
| 132 | private static final int BSSID_STAT_RANGE_LOW_DBM = -105; |
| 133 | |
| 134 | /** |
| 135 | * See {@link #BSSID_STAT_RANGE_LOW_DBM}. |
| 136 | */ |
| 137 | private static final int BSSID_STAT_RANGE_HIGH_DBM = -45; |
| 138 | |
| 139 | /** |
| 140 | * How many consecutive empty data point to trigger a empty-cache detection. |
| 141 | * In this case, a preset/default loss value (function on RSSI) is used. |
| 142 | * <p> |
| 143 | * In normal uses, some RSSI values may never be seen due to channel randomness. |
| 144 | * However, the size of such empty RSSI chunk in normal use is generally 1~2. |
| 145 | */ |
| 146 | private static final int BSSID_STAT_EMPTY_COUNT = 3; |
| 147 | |
| 148 | /** |
| 149 | * Sample interval for packet loss statistics, in msec. |
| 150 | * <p> |
| 151 | * Smaller interval is more accurate but increases sampling cost (battery consumption). |
| 152 | */ |
| 153 | private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000; |
| 154 | |
| 155 | /** |
| 156 | * Coefficients (alpha) for moving average for packet loss tracking. |
| 157 | * Must be within (0.0, 1.0). |
| 158 | * <p> |
| 159 | * Equivalent number of samples: N = 2 / alpha - 1 . |
| 160 | * We want the historic loss to base on more data points to be statistically reliable. |
| 161 | * We want the current instant loss to base on less data points to be responsive. |
| 162 | */ |
| 163 | private static final double EXP_COEFFICIENT_RECORD = 0.1; |
| 164 | |
| 165 | /** |
| 166 | * See {@link #EXP_COEFFICIENT_RECORD}. |
| 167 | */ |
| 168 | private static final double EXP_COEFFICIENT_MONITOR = 0.5; |
| 169 | |
| 170 | /** |
| 171 | * Thresholds for sending good/poor link notifications, in packet loss %. |
| 172 | * Good threshold must be smaller than poor threshold. |
| 173 | * Use smaller poor threshold to avoid WiFi more aggressively. |
| 174 | * Use smaller good threshold to bring back WiFi more conservatively. |
| 175 | * <p> |
| 176 | * When approaching the boundary, loss ratio jumps significantly within a few dBs. |
| 177 | * 50% loss threshold is a good balance between accuracy and reponsiveness. |
| 178 | * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily. |
| 179 | */ |
| 180 | private static final double POOR_LINK_LOSS_THRESHOLD = 0.5; |
| 181 | |
| 182 | /** |
| 183 | * See {@link #POOR_LINK_LOSS_THRESHOLD}. |
| 184 | */ |
| 185 | private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1; |
| 186 | |
| 187 | /** |
| 188 | * Number of samples to confirm before sending a poor link notification. |
| 189 | * Response time = confirm_count * sample_interval . |
| 190 | * <p> |
| 191 | * A smaller threshold improves response speed but may suffer from randomness. |
| 192 | * According to experiments, 3~5 are good values to achieve a balance. |
| 193 | * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}. |
| 194 | */ |
| 195 | private static final int POOR_LINK_SAMPLE_COUNT = 3; |
| 196 | |
| 197 | /** |
| 198 | * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness. |
| 199 | * <p> |
| 200 | * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive. |
| 201 | */ |
| 202 | private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0; |
| 203 | |
| 204 | /** |
| 205 | * When a poor link is detected, we scan over this range (based on current |
| 206 | * poor link RSSI) for a target RSSI that satisfies a target packet loss. |
| 207 | * Refer to {@link #GOOD_LINK_TARGET}. |
| 208 | * <p> |
| 209 | * We want range_min not too small to avoid jumping back to WiFi too easily. |
| 210 | */ |
| 211 | private static final int GOOD_LINK_RSSI_RANGE_MIN = 3; |
| 212 | |
| 213 | /** |
| 214 | * See {@link #GOOD_LINK_RSSI_RANGE_MIN}. |
| 215 | */ |
| 216 | private static final int GOOD_LINK_RSSI_RANGE_MAX = 20; |
| 217 | |
| 218 | /** |
| 219 | * Adaptive good link target to avoid flapping. |
| 220 | * When a poor link is detected, a good link target is calculated as follows: |
| 221 | * <p> |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 222 | * targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i], |
| 223 | * where rssi is within the above GOOD_LINK_RSSI_RANGE. |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 224 | * targetCount = sample_count[i] . |
| 225 | * <p> |
| 226 | * While WiFi is being avoided, we keep monitoring its signal strength. |
| 227 | * Good link notification is sent when we see current RSSI >= targetRSSI |
| 228 | * for targetCount consecutive times. |
| 229 | * <p> |
| 230 | * Index i is incremented each time after a poor link detection. |
| 231 | * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago. |
| 232 | * <p> |
| 233 | * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping. |
| 234 | * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve. |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 235 | * Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago). |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 236 | */ |
| 237 | private static final GoodLinkTarget[] GOOD_LINK_TARGET = { |
| 238 | /* rssi_adj, sample_count, reduce_time */ |
| 239 | new GoodLinkTarget( 0, 3, 30 * 60000 ), |
| 240 | new GoodLinkTarget( 3, 5, 5 * 60000 ), |
| 241 | new GoodLinkTarget( 6, 10, 1 * 60000 ), |
| 242 | new GoodLinkTarget( 9, 30, 0 * 60000 ), |
| 243 | }; |
| 244 | |
| 245 | /** |
| 246 | * The max time to avoid a BSSID, to prevent avoiding forever. |
| 247 | * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i] |
| 248 | * <p> |
| 249 | * It is unusual to experience high packet loss at high RSSI. Something unusual must be |
| 250 | * happening (e.g. strong interference). For higher signal strengths, we set the avoidance |
| 251 | * time to be low to allow for quick turn around from temporary interference. |
| 252 | * <p> |
| 253 | * See {@link BssidStatistics#poorLinkDetected}. |
| 254 | */ |
| 255 | private static final MaxAvoidTime[] MAX_AVOID_TIME = { |
| 256 | /* max_time, min_rssi */ |
| 257 | new MaxAvoidTime( 30 * 60000, -200 ), |
| 258 | new MaxAvoidTime( 5 * 60000, -70 ), |
| 259 | new MaxAvoidTime( 0 * 60000, -55 ), |
| 260 | }; |
| 261 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 262 | /* Framework related */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 263 | private Context mContext; |
| 264 | private ContentResolver mContentResolver; |
| 265 | private WifiManager mWifiManager; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 266 | private IntentFilter mIntentFilter; |
| 267 | private BroadcastReceiver mBroadcastReceiver; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 268 | private AsyncChannel mWsmChannel = new AsyncChannel(); |
| 269 | private WifiInfo mWifiInfo; |
| 270 | private LinkProperties mLinkProperties; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 271 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 272 | /* System settingss related */ |
| 273 | private static boolean sWifiOnly = false; |
| 274 | private boolean mPoorNetworkDetectionEnabled; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 275 | |
| 276 | /* Poor link detection related */ |
| 277 | private LruCache<String, BssidStatistics> mBssidCache = |
| 278 | new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE); |
| 279 | private int mRssiFetchToken = 0; |
| 280 | private int mCurrentSignalLevel; |
| 281 | private BssidStatistics mCurrentBssid; |
| 282 | private VolumeWeightedEMA mCurrentLoss; |
| 283 | private boolean mIsScreenOn = true; |
| 284 | private static double sPresetLoss[]; |
| 285 | |
| 286 | /* WiFi watchdog state machine related */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 287 | private DefaultState mDefaultState = new DefaultState(); |
| 288 | private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); |
| 289 | private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); |
| 290 | private NotConnectedState mNotConnectedState = new NotConnectedState(); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 291 | private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 292 | private ConnectedState mConnectedState = new ConnectedState(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 293 | private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 294 | private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState(); |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 295 | private OnlineState mOnlineState = new OnlineState(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 296 | |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 297 | /** |
| 298 | * STATE MAP |
| 299 | * Default |
| 300 | * / \ |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 301 | * Disabled Enabled |
| 302 | * / \ \ |
| 303 | * NotConnected Verifying Connected |
| 304 | * /---------\ |
| 305 | * (all other states) |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 306 | */ |
| 307 | private WifiWatchdogStateMachine(Context context) { |
| Wink Saville | 58c73c3 | 2013-01-28 10:52:34 -0800 | [diff] [blame] | 308 | super("WifiWatchdogStateMachine"); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 309 | mContext = context; |
| 310 | mContentResolver = context.getContentResolver(); |
| 311 | mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 312 | mWsmChannel.connectSync(mContext, getHandler(), |
| 313 | mWifiManager.getWifiStateMachineMessenger()); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 314 | |
| 315 | setupNetworkReceiver(); |
| 316 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 317 | // the content observer to listen needs a handler |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 318 | registerForSettingsChanges(); |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 319 | registerForWatchdogToggle(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 320 | addState(mDefaultState); |
| 321 | addState(mWatchdogDisabledState, mDefaultState); |
| 322 | addState(mWatchdogEnabledState, mDefaultState); |
| 323 | addState(mNotConnectedState, mWatchdogEnabledState); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 324 | addState(mVerifyingLinkState, mWatchdogEnabledState); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 325 | addState(mConnectedState, mWatchdogEnabledState); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 326 | addState(mOnlineWatchState, mConnectedState); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 327 | addState(mLinkMonitoringState, mConnectedState); |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 328 | addState(mOnlineState, mConnectedState); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 329 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 330 | if (isWatchdogEnabled()) { |
| 331 | setInitialState(mNotConnectedState); |
| 332 | } else { |
| 333 | setInitialState(mWatchdogDisabledState); |
| 334 | } |
| Irfan Sheriff | f0afe41 | 2012-11-30 14:07:44 -0800 | [diff] [blame] | 335 | setLogRecSize(25); |
| 336 | setLogOnlyTransitions(true); |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 337 | updateSettings(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 338 | } |
| 339 | |
| 340 | public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) { |
| Isaac Levy | 4ad39d6 | 2011-07-26 18:59:12 -0700 | [diff] [blame] | 341 | ContentResolver contentResolver = context.getContentResolver(); |
| Robert Greenwalt | 9b2886e | 2011-08-31 11:46:42 -0700 | [diff] [blame] | 342 | |
| 343 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService( |
| 344 | Context.CONNECTIVITY_SERVICE); |
| 345 | sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); |
| 346 | |
| Irfan Sheriff | da6da09 | 2012-08-16 12:49:23 -0700 | [diff] [blame] | 347 | // Watchdog is always enabled. Poor network detection can be seperately turned on/off |
| Irfan Sheriff | ae094b2 | 2012-05-30 16:52:38 -0700 | [diff] [blame] | 348 | // TODO: Remove this setting & clean up state machine since we always have |
| 349 | // watchdog in an enabled state |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 350 | putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true); |
| Irfan Sheriff | ae094b2 | 2012-05-30 16:52:38 -0700 | [diff] [blame] | 351 | |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 352 | WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); |
| 353 | wwsm.start(); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 354 | return wwsm; |
| 355 | } |
| 356 | |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 357 | private void setupNetworkReceiver() { |
| 358 | mBroadcastReceiver = new BroadcastReceiver() { |
| 359 | @Override |
| 360 | public void onReceive(Context context, Intent intent) { |
| 361 | String action = intent.getAction(); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 362 | if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 363 | obtainMessage(EVENT_RSSI_CHANGE, |
| 364 | intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 365 | } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { |
| 366 | sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent); |
| 367 | } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { |
| 368 | sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); |
| 369 | } else if (action.equals(Intent.ACTION_SCREEN_ON)) { |
| 370 | sendMessage(EVENT_SCREEN_ON); |
| 371 | } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { |
| 372 | sendMessage(EVENT_SCREEN_OFF); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 373 | } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 374 | sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra( |
| 375 | WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 376 | } |
| 377 | } |
| 378 | }; |
| 379 | |
| 380 | mIntentFilter = new IntentFilter(); |
| 381 | mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); |
| 382 | mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); |
| 383 | mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 384 | mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); |
| 385 | mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); |
| 386 | mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 387 | mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 388 | } |
| 389 | |
| 390 | /** |
| 391 | * Observes the watchdog on/off setting, and takes action when changed. |
| 392 | */ |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 393 | private void registerForWatchdogToggle() { |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 394 | ContentObserver contentObserver = new ContentObserver(this.getHandler()) { |
| 395 | @Override |
| 396 | public void onChange(boolean selfChange) { |
| 397 | sendMessage(EVENT_WATCHDOG_TOGGLED); |
| 398 | } |
| 399 | }; |
| 400 | |
| 401 | mContext.getContentResolver().registerContentObserver( |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 402 | Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_ON), |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 403 | false, contentObserver); |
| 404 | } |
| 405 | |
| 406 | /** |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 407 | * Observes watchdogs secure setting changes. |
| 408 | */ |
| 409 | private void registerForSettingsChanges() { |
| 410 | ContentObserver contentObserver = new ContentObserver(this.getHandler()) { |
| 411 | @Override |
| 412 | public void onChange(boolean selfChange) { |
| 413 | sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE); |
| 414 | } |
| 415 | }; |
| 416 | |
| 417 | mContext.getContentResolver().registerContentObserver( |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 418 | Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 419 | false, contentObserver); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 420 | } |
| 421 | |
| Irfan Sheriff | f0afe41 | 2012-11-30 14:07:44 -0800 | [diff] [blame] | 422 | public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| 423 | super.dump(fd, pw, args); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 424 | pw.println("mWifiInfo: [" + mWifiInfo + "]"); |
| 425 | pw.println("mLinkProperties: [" + mLinkProperties + "]"); |
| 426 | pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 427 | pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 428 | } |
| 429 | |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 430 | private boolean isWatchdogEnabled() { |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 431 | boolean ret = getSettingsGlobalBoolean( |
| 432 | mContentResolver, Settings.Global.WIFI_WATCHDOG_ON, true); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 433 | if (DBG) logd("Watchdog enabled " + ret); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 434 | return ret; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 435 | } |
| 436 | |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 437 | private void updateSettings() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 438 | if (DBG) logd("Updating secure settings"); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 439 | |
| Irfan Sheriff | bbe461b | 2012-10-09 15:49:33 -0700 | [diff] [blame] | 440 | // disable poor network avoidance |
| 441 | if (sWifiOnly) { |
| 442 | logd("Disabling poor network avoidance for wi-fi only device"); |
| 443 | mPoorNetworkDetectionEnabled = false; |
| 444 | } else { |
| 445 | mPoorNetworkDetectionEnabled = getSettingsGlobalBoolean(mContentResolver, |
| Irfan Sheriff | 17cf1f2 | 2012-10-09 17:08:39 -0700 | [diff] [blame] | 446 | Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, |
| 447 | DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED); |
| Irfan Sheriff | bbe461b | 2012-10-09 15:49:33 -0700 | [diff] [blame] | 448 | } |
| Irfan Sheriff | 6b66e9e | 2011-10-24 16:55:50 -0700 | [diff] [blame] | 449 | } |
| 450 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 451 | /** |
| 452 | * Default state, guard for unhandled messages. |
| 453 | */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 454 | class DefaultState extends State { |
| 455 | @Override |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 456 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 457 | if (DBG) logd(getName()); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 458 | } |
| 459 | |
| 460 | @Override |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 461 | public boolean processMessage(Message msg) { |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 462 | switch (msg.what) { |
| 463 | case EVENT_WATCHDOG_SETTINGS_CHANGE: |
| 464 | updateSettings(); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 465 | if (DBG) logd("Updating wifi-watchdog secure settings"); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 466 | break; |
| 467 | case EVENT_RSSI_CHANGE: |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 468 | mCurrentSignalLevel = calculateSignalLevel(msg.arg1); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 469 | break; |
| 470 | case EVENT_WIFI_RADIO_STATE_CHANGE: |
| 471 | case EVENT_NETWORK_STATE_CHANGE: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 472 | case EVENT_SUPPLICANT_STATE_CHANGE: |
| 473 | case EVENT_BSSID_CHANGE: |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 474 | case CMD_RSSI_FETCH: |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 475 | case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: |
| 476 | case WifiManager.RSSI_PKTCNT_FETCH_FAILED: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 477 | // ignore |
| 478 | break; |
| 479 | case EVENT_SCREEN_ON: |
| 480 | mIsScreenOn = true; |
| 481 | break; |
| 482 | case EVENT_SCREEN_OFF: |
| 483 | mIsScreenOn = false; |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 484 | break; |
| 485 | default: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 486 | loge("Unhandled message " + msg + " in state " + getCurrentState().getName()); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 487 | break; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 488 | } |
| 489 | return HANDLED; |
| 490 | } |
| 491 | } |
| 492 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 493 | /** |
| 494 | * WiFi watchdog is disabled by the setting. |
| 495 | */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 496 | class WatchdogDisabledState extends State { |
| 497 | @Override |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 498 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 499 | if (DBG) logd(getName()); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 500 | } |
| 501 | |
| 502 | @Override |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 503 | public boolean processMessage(Message msg) { |
| 504 | switch (msg.what) { |
| 505 | case EVENT_WATCHDOG_TOGGLED: |
| 506 | if (isWatchdogEnabled()) |
| 507 | transitionTo(mNotConnectedState); |
| 508 | return HANDLED; |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 509 | case EVENT_NETWORK_STATE_CHANGE: |
| 510 | Intent intent = (Intent) msg.obj; |
| 511 | NetworkInfo networkInfo = (NetworkInfo) |
| 512 | intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); |
| 513 | |
| 514 | switch (networkInfo.getDetailedState()) { |
| 515 | case VERIFYING_POOR_LINK: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 516 | if (DBG) logd("Watchdog disabled, verify link"); |
| 517 | sendLinkStatusNotification(true); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 518 | break; |
| 519 | default: |
| 520 | break; |
| 521 | } |
| 522 | break; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 523 | } |
| 524 | return NOT_HANDLED; |
| 525 | } |
| 526 | } |
| 527 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 528 | /** |
| 529 | * WiFi watchdog is enabled by the setting. |
| 530 | */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 531 | class WatchdogEnabledState extends State { |
| 532 | @Override |
| 533 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 534 | if (DBG) logd(getName()); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 535 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 536 | |
| 537 | @Override |
| 538 | public boolean processMessage(Message msg) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 539 | Intent intent; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 540 | switch (msg.what) { |
| 541 | case EVENT_WATCHDOG_TOGGLED: |
| 542 | if (!isWatchdogEnabled()) |
| 543 | transitionTo(mWatchdogDisabledState); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 544 | break; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 545 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 546 | case EVENT_NETWORK_STATE_CHANGE: |
| 547 | intent = (Intent) msg.obj; |
| 548 | NetworkInfo networkInfo = |
| 549 | (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); |
| 550 | if (DBG) logd("Network state change " + networkInfo.getDetailedState()); |
| 551 | |
| 552 | mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); |
| 553 | updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 554 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 555 | switch (networkInfo.getDetailedState()) { |
| 556 | case VERIFYING_POOR_LINK: |
| 557 | mLinkProperties = (LinkProperties) intent.getParcelableExtra( |
| 558 | WifiManager.EXTRA_LINK_PROPERTIES); |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 559 | if (mPoorNetworkDetectionEnabled) { |
| Irfan Sheriff | edba852 | 2012-10-16 18:10:38 -0700 | [diff] [blame] | 560 | if (mWifiInfo == null || mCurrentBssid == null) { |
| 561 | loge("Ignore, wifiinfo " + mWifiInfo +" bssid " + mCurrentBssid); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 562 | sendLinkStatusNotification(true); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 563 | } else { |
| 564 | transitionTo(mVerifyingLinkState); |
| 565 | } |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 566 | } else { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 567 | sendLinkStatusNotification(true); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 568 | } |
| 569 | break; |
| 570 | case CONNECTED: |
| Irfan Sheriff | da6da09 | 2012-08-16 12:49:23 -0700 | [diff] [blame] | 571 | transitionTo(mOnlineWatchState); |
| Irfan Sheriff | 32f04e9 | 2011-09-23 14:48:26 -0700 | [diff] [blame] | 572 | break; |
| 573 | default: |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 574 | transitionTo(mNotConnectedState); |
| Irfan Sheriff | 32f04e9 | 2011-09-23 14:48:26 -0700 | [diff] [blame] | 575 | break; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 576 | } |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 577 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 578 | |
| 579 | case EVENT_SUPPLICANT_STATE_CHANGE: |
| 580 | intent = (Intent) msg.obj; |
| 581 | SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra( |
| 582 | WifiManager.EXTRA_NEW_STATE); |
| 583 | if (supplicantState == SupplicantState.COMPLETED) { |
| 584 | mWifiInfo = mWifiManager.getConnectionInfo(); |
| 585 | updateCurrentBssid(mWifiInfo.getBSSID()); |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 586 | } |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 587 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 588 | |
| 589 | case EVENT_WIFI_RADIO_STATE_CHANGE: |
| Irfan Sheriff | e6db500 | 2013-03-14 14:29:26 -0700 | [diff] [blame] | 590 | if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 591 | transitionTo(mNotConnectedState); |
| Irfan Sheriff | e6db500 | 2013-03-14 14:29:26 -0700 | [diff] [blame] | 592 | } |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 593 | break; |
| 594 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 595 | default: |
| 596 | return NOT_HANDLED; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 597 | } |
| 598 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 599 | return HANDLED; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 600 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 601 | } |
| 602 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 603 | /** |
| 604 | * WiFi is disconnected. |
| 605 | */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 606 | class NotConnectedState extends State { |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 607 | @Override |
| 608 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 609 | if (DBG) logd(getName()); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 610 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 611 | } |
| 612 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 613 | /** |
| 614 | * WiFi is connected, but waiting for good link detection message. |
| 615 | */ |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 616 | class VerifyingLinkState extends State { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 617 | |
| 618 | private int mSampleCount; |
| 619 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 620 | @Override |
| 621 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 622 | if (DBG) logd(getName()); |
| 623 | mSampleCount = 0; |
| 624 | mCurrentBssid.newLinkDetected(); |
| 625 | sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 626 | } |
| 627 | |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 628 | @Override |
| 629 | public boolean processMessage(Message msg) { |
| 630 | switch (msg.what) { |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 631 | case EVENT_WATCHDOG_SETTINGS_CHANGE: |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 632 | updateSettings(); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 633 | if (!mPoorNetworkDetectionEnabled) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 634 | sendLinkStatusNotification(true); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 635 | } |
| 636 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 637 | |
| 638 | case EVENT_BSSID_CHANGE: |
| 639 | transitionTo(mVerifyingLinkState); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 640 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 641 | |
| 642 | case CMD_RSSI_FETCH: |
| 643 | if (msg.arg1 == mRssiFetchToken) { |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 644 | mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 645 | sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), |
| 646 | LINK_SAMPLING_INTERVAL_MS); |
| 647 | } |
| 648 | break; |
| 649 | |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 650 | case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: |
| 651 | RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj; |
| 652 | int rssi = info.rssi; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 653 | if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi); |
| 654 | |
| 655 | long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime(); |
| 656 | if (time <= 0) { |
| 657 | // max avoidance time is met |
| 658 | if (DBG) logd("Max avoid time elapsed"); |
| 659 | sendLinkStatusNotification(true); |
| 660 | } else { |
| 661 | if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) { |
| 662 | if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) { |
| 663 | // link is good again |
| 664 | if (DBG) logd("Good link detected, rssi=" + rssi); |
| 665 | mCurrentBssid.mBssidAvoidTimeMax = 0; |
| 666 | sendLinkStatusNotification(true); |
| 667 | } |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 668 | } else { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 669 | mSampleCount = 0; |
| 670 | if (DBG) logd("Link is still poor, time left=" + time); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 671 | } |
| 672 | } |
| 673 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 674 | |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 675 | case WifiManager.RSSI_PKTCNT_FETCH_FAILED: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 676 | if (DBG) logd("RSSI_FETCH_FAILED"); |
| 677 | break; |
| 678 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 679 | default: |
| 680 | return NOT_HANDLED; |
| 681 | } |
| 682 | return HANDLED; |
| 683 | } |
| 684 | } |
| 685 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 686 | /** |
| 687 | * WiFi is connected and link is verified. |
| 688 | */ |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 689 | class ConnectedState extends State { |
| 690 | @Override |
| 691 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 692 | if (DBG) logd(getName()); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 693 | } |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 694 | |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 695 | @Override |
| 696 | public boolean processMessage(Message msg) { |
| 697 | switch (msg.what) { |
| 698 | case EVENT_WATCHDOG_SETTINGS_CHANGE: |
| 699 | updateSettings(); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 700 | // STOPSHIP: Remove this at ship |
| 701 | logd("Updated secure settings and turned debug on"); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 702 | DBG = true; |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 703 | |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 704 | if (mPoorNetworkDetectionEnabled) { |
| 705 | transitionTo(mOnlineWatchState); |
| 706 | } else { |
| 707 | transitionTo(mOnlineState); |
| 708 | } |
| 709 | return HANDLED; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 710 | } |
| 711 | return NOT_HANDLED; |
| 712 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 713 | } |
| 714 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 715 | /** |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 716 | * RSSI is high enough and don't need link monitoring. |
| 717 | */ |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 718 | class OnlineWatchState extends State { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 719 | @Override |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 720 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 721 | if (DBG) logd(getName()); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 722 | if (mPoorNetworkDetectionEnabled) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 723 | // treat entry as an rssi change |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 724 | handleRssiChange(); |
| 725 | } else { |
| 726 | transitionTo(mOnlineState); |
| 727 | } |
| 728 | } |
| 729 | |
| 730 | private void handleRssiChange() { |
| Irfan Sheriff | edba852 | 2012-10-16 18:10:38 -0700 | [diff] [blame] | 731 | if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD && mCurrentBssid != null) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 732 | transitionTo(mLinkMonitoringState); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 733 | } else { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 734 | // stay here |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 735 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 736 | } |
| 737 | |
| 738 | @Override |
| 739 | public boolean processMessage(Message msg) { |
| 740 | switch (msg.what) { |
| 741 | case EVENT_RSSI_CHANGE: |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 742 | mCurrentSignalLevel = calculateSignalLevel(msg.arg1); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 743 | handleRssiChange(); |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 744 | break; |
| 745 | default: |
| 746 | return NOT_HANDLED; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 747 | } |
| Irfan Sheriff | 07573b3 | 2012-01-27 21:00:19 -0800 | [diff] [blame] | 748 | return HANDLED; |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 749 | } |
| 750 | } |
| 751 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 752 | /** |
| 753 | * Keep sampling the link and monitor any poor link situation. |
| 754 | */ |
| 755 | class LinkMonitoringState extends State { |
| 756 | |
| 757 | private int mSampleCount; |
| 758 | |
| 759 | private int mLastRssi; |
| 760 | private int mLastTxGood; |
| 761 | private int mLastTxBad; |
| 762 | |
| 763 | @Override |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 764 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 765 | if (DBG) logd(getName()); |
| 766 | mSampleCount = 0; |
| 767 | mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 768 | sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); |
| 769 | } |
| 770 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 771 | @Override |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 772 | public boolean processMessage(Message msg) { |
| 773 | switch (msg.what) { |
| 774 | case EVENT_RSSI_CHANGE: |
| 775 | mCurrentSignalLevel = calculateSignalLevel(msg.arg1); |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 776 | if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) { |
| 777 | // stay here; |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 778 | } else { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 779 | // we don't need frequent RSSI monitoring any more |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 780 | transitionTo(mOnlineWatchState); |
| 781 | } |
| 782 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 783 | |
| 784 | case EVENT_BSSID_CHANGE: |
| 785 | transitionTo(mLinkMonitoringState); |
| 786 | break; |
| 787 | |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 788 | case CMD_RSSI_FETCH: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 789 | if (!mIsScreenOn) { |
| 790 | transitionTo(mOnlineState); |
| 791 | } else if (msg.arg1 == mRssiFetchToken) { |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 792 | mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 793 | sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 794 | LINK_SAMPLING_INTERVAL_MS); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 795 | } |
| 796 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 797 | |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 798 | case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: |
| 799 | RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj; |
| 800 | int rssi = info.rssi; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 801 | int mrssi = (mLastRssi + rssi) / 2; |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 802 | int txbad = info.txbad; |
| 803 | int txgood = info.txgood; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 804 | if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad=" |
| 805 | + txbad + " txgood=" + txgood); |
| 806 | |
| 807 | // skip the first data point as we want incremental values |
| 808 | long now = SystemClock.elapsedRealtime(); |
| 809 | if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) { |
| 810 | |
| 811 | // update packet loss statistics |
| 812 | int dbad = txbad - mLastTxBad; |
| 813 | int dgood = txgood - mLastTxGood; |
| 814 | int dtotal = dbad + dgood; |
| 815 | |
| 816 | if (dtotal > 0) { |
| 817 | // calculate packet loss in the last sampling interval |
| 818 | double loss = ((double) dbad) / ((double) dtotal); |
| 819 | |
| 820 | mCurrentLoss.update(loss, dtotal); |
| 821 | |
| 822 | if (DBG) { |
| 823 | DecimalFormat df = new DecimalFormat("#.##"); |
| 824 | logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss=" |
| 825 | + df.format(mCurrentLoss.mValue * 100) + "% volume=" |
| 826 | + df.format(mCurrentLoss.mVolume)); |
| 827 | } |
| 828 | |
| 829 | mCurrentBssid.updateLoss(mrssi, loss, dtotal); |
| 830 | |
| 831 | // check for high packet loss and send poor link notification |
| 832 | if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD |
| 833 | && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) { |
| 834 | if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT) |
| 835 | if (mCurrentBssid.poorLinkDetected(rssi)) { |
| 836 | sendLinkStatusNotification(false); |
| 837 | ++mRssiFetchToken; |
| 838 | } |
| 839 | } else { |
| 840 | mSampleCount = 0; |
| 841 | } |
| 842 | } |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 843 | } |
| 844 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 845 | mCurrentBssid.mLastTimeSample = now; |
| 846 | mLastTxBad = txbad; |
| 847 | mLastTxGood = txgood; |
| 848 | mLastRssi = rssi; |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 849 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 850 | |
| Yuhao Zheng | f630782 | 2012-08-14 14:21:25 -0700 | [diff] [blame] | 851 | case WifiManager.RSSI_PKTCNT_FETCH_FAILED: |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 852 | // can happen if we are waiting to get a disconnect notification |
| 853 | if (DBG) logd("RSSI_FETCH_FAILED"); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 854 | break; |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 855 | |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 856 | default: |
| 857 | return NOT_HANDLED; |
| 858 | } |
| 859 | return HANDLED; |
| 860 | } |
| 861 | } |
| 862 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 863 | /** |
| 864 | * Child state of ConnectedState indicating that we are online and there is nothing to do. |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 865 | */ |
| 866 | class OnlineState extends State { |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 867 | @Override |
| 868 | public void enter() { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 869 | if (DBG) logd(getName()); |
| 870 | } |
| 871 | |
| 872 | @Override |
| 873 | public boolean processMessage(Message msg) { |
| 874 | switch (msg.what) { |
| 875 | case EVENT_SCREEN_ON: |
| 876 | mIsScreenOn = true; |
| 877 | if (mPoorNetworkDetectionEnabled) |
| 878 | transitionTo(mOnlineWatchState); |
| 879 | break; |
| 880 | default: |
| 881 | return NOT_HANDLED; |
| 882 | } |
| 883 | return HANDLED; |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 884 | } |
| Irfan Sheriff | 19380da | 2011-12-06 14:41:14 -0800 | [diff] [blame] | 885 | } |
| 886 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 887 | private void updateCurrentBssid(String bssid) { |
| 888 | if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null")); |
| 889 | |
| 890 | // if currently not connected, then set current BSSID to null |
| 891 | if (bssid == null) { |
| 892 | if (mCurrentBssid == null) return; |
| 893 | mCurrentBssid = null; |
| 894 | if (DBG) logd("BSSID changed"); |
| 895 | sendMessage(EVENT_BSSID_CHANGE); |
| 896 | return; |
| 897 | } |
| 898 | |
| 899 | // if it is already the current BSSID, then done |
| 900 | if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return; |
| 901 | |
| 902 | // search for the new BSSID in the cache, add to cache if not found |
| 903 | mCurrentBssid = mBssidCache.get(bssid); |
| 904 | if (mCurrentBssid == null) { |
| 905 | mCurrentBssid = new BssidStatistics(bssid); |
| 906 | mBssidCache.put(bssid, mCurrentBssid); |
| 907 | } |
| 908 | |
| 909 | // send BSSID change notification |
| 910 | if (DBG) logd("BSSID changed"); |
| 911 | sendMessage(EVENT_BSSID_CHANGE); |
| 912 | } |
| 913 | |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 914 | private int calculateSignalLevel(int rssi) { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 915 | int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS); |
| 916 | if (DBG) |
| 917 | logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel); |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 918 | return signalLevel; |
| 919 | } |
| 920 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 921 | private void sendLinkStatusNotification(boolean isGood) { |
| 922 | if (DBG) logd("########################################"); |
| 923 | if (isGood) { |
| 924 | mWsmChannel.sendMessage(GOOD_LINK_DETECTED); |
| Irfan Sheriff | edba852 | 2012-10-16 18:10:38 -0700 | [diff] [blame] | 925 | if (mCurrentBssid != null) { |
| 926 | mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime(); |
| 927 | } |
| 928 | if (DBG) logd("Good link notification is sent"); |
| Irfan Sheriff | 90d57df | 2012-03-12 15:55:20 -0700 | [diff] [blame] | 929 | } else { |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 930 | mWsmChannel.sendMessage(POOR_LINK_DETECTED); |
| Irfan Sheriff | edba852 | 2012-10-16 18:10:38 -0700 | [diff] [blame] | 931 | if (mCurrentBssid != null) { |
| 932 | mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime(); |
| 933 | } |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 934 | logd("Poor link notification is sent"); |
| Irfan Sheriff | 90d57df | 2012-03-12 15:55:20 -0700 | [diff] [blame] | 935 | } |
| Irfan Sheriff | 4c8982a | 2012-03-08 16:11:02 -0800 | [diff] [blame] | 936 | } |
| 937 | |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 938 | /** |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 939 | * Convenience function for retrieving a single secure settings value as a |
| 940 | * boolean. Note that internally setting values are always stored as |
| 941 | * strings; this function converts the string to a boolean for you. The |
| 942 | * default value will be returned if the setting is not defined or not a |
| 943 | * valid boolean. |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 944 | * |
| 945 | * @param cr The ContentResolver to access. |
| 946 | * @param name The name of the setting to retrieve. |
| 947 | * @param def Value to return if the setting is not defined. |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 948 | * @return The setting's current value, or 'def' if it is not defined or not |
| 949 | * a valid boolean. |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 950 | */ |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 951 | private static boolean getSettingsGlobalBoolean(ContentResolver cr, String name, boolean def) { |
| 952 | return Settings.Global.getInt(cr, name, def ? 1 : 0) == 1; |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 953 | } |
| 954 | |
| 955 | /** |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 956 | * Convenience function for updating a single settings value as an integer. |
| 957 | * This will either create a new entry in the table if the given name does |
| 958 | * not exist, or modify the value of the existing row with that name. Note |
| 959 | * that internally setting values are always stored as strings, so this |
| 960 | * function converts the given value to a string before storing it. |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 961 | * |
| 962 | * @param cr The ContentResolver to access. |
| 963 | * @param name The name of the setting to modify. |
| 964 | * @param value The new value for the setting. |
| 965 | * @return true if the value was set, false on database errors |
| 966 | */ |
| Jeff Sharkey | bdfce2e | 2012-09-26 15:54:06 -0700 | [diff] [blame] | 967 | private static boolean putSettingsGlobalBoolean(ContentResolver cr, String name, boolean value) { |
| 968 | return Settings.Global.putInt(cr, name, value ? 1 : 0); |
| Isaac Levy | d7b3e6a | 2011-07-20 18:15:30 -0700 | [diff] [blame] | 969 | } |
| 970 | |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 971 | /** |
| Yuhao Zheng | b33227d | 2012-07-20 10:55:17 -0700 | [diff] [blame] | 972 | * Bundle of good link count parameters |
| 973 | */ |
| 974 | private static class GoodLinkTarget { |
| 975 | public final int RSSI_ADJ_DBM; |
| 976 | public final int SAMPLE_COUNT; |
| 977 | public final int REDUCE_TIME_MS; |
| 978 | public GoodLinkTarget(int adj, int count, int time) { |
| 979 | RSSI_ADJ_DBM = adj; |
| 980 | SAMPLE_COUNT = count; |
| 981 | REDUCE_TIME_MS = time; |
| 982 | } |
| 983 | } |
| 984 | |
| 985 | /** |
| 986 | * Bundle of max avoidance time parameters |
| 987 | */ |
| 988 | private static class MaxAvoidTime { |
| 989 | public final int TIME_MS; |
| 990 | public final int MIN_RSSI_DBM; |
| 991 | public MaxAvoidTime(int time, int rssi) { |
| 992 | TIME_MS = time; |
| 993 | MIN_RSSI_DBM = rssi; |
| 994 | } |
| 995 | } |
| 996 | |
| 997 | /** |
| 998 | * Volume-weighted Exponential Moving Average (V-EMA) |
| 999 | * - volume-weighted: each update has its own weight (number of packets) |
| 1000 | * - exponential: O(1) time and O(1) space for both update and query |
| 1001 | * - moving average: reflect most recent results and expire old ones |
| 1002 | */ |
| 1003 | private class VolumeWeightedEMA { |
| 1004 | private double mValue; |
| 1005 | private double mVolume; |
| 1006 | private double mProduct; |
| 1007 | private final double mAlpha; |
| 1008 | |
| 1009 | public VolumeWeightedEMA(double coefficient) { |
| 1010 | mValue = 0.0; |
| 1011 | mVolume = 0.0; |
| 1012 | mProduct = 0.0; |
| 1013 | mAlpha = coefficient; |
| 1014 | } |
| 1015 | |
| 1016 | public void update(double newValue, int newVolume) { |
| 1017 | if (newVolume <= 0) return; |
| 1018 | // core update formulas |
| 1019 | double newProduct = newValue * newVolume; |
| 1020 | mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct; |
| 1021 | mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume; |
| 1022 | mValue = mProduct / mVolume; |
| 1023 | } |
| 1024 | } |
| 1025 | |
| 1026 | /** |
| 1027 | * Record (RSSI -> pakce loss %) mappings of one BSSID |
| 1028 | */ |
| 1029 | private class BssidStatistics { |
| 1030 | |
| 1031 | /* MAC address of this BSSID */ |
| 1032 | private final String mBssid; |
| 1033 | |
| 1034 | /* RSSI -> packet loss % mappings */ |
| 1035 | private VolumeWeightedEMA[] mEntries; |
| 1036 | private int mRssiBase; |
| 1037 | private int mEntriesSize; |
| 1038 | |
| 1039 | /* Target to send good link notification, set when poor link is detected */ |
| 1040 | private int mGoodLinkTargetRssi; |
| 1041 | private int mGoodLinkTargetCount; |
| 1042 | |
| 1043 | /* Index of GOOD_LINK_TARGET array */ |
| 1044 | private int mGoodLinkTargetIndex; |
| 1045 | |
| 1046 | /* Timestamps of some last events */ |
| 1047 | private long mLastTimeSample; |
| 1048 | private long mLastTimeGood; |
| 1049 | private long mLastTimePoor; |
| 1050 | |
| 1051 | /* Max time to avoid this BSSID */ |
| 1052 | private long mBssidAvoidTimeMax; |
| 1053 | |
| 1054 | /** |
| 1055 | * Constructor |
| 1056 | * |
| 1057 | * @param bssid is the address of this BSSID |
| 1058 | */ |
| 1059 | public BssidStatistics(String bssid) { |
| 1060 | this.mBssid = bssid; |
| 1061 | mRssiBase = BSSID_STAT_RANGE_LOW_DBM; |
| 1062 | mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1; |
| 1063 | mEntries = new VolumeWeightedEMA[mEntriesSize]; |
| 1064 | for (int i = 0; i < mEntriesSize; i++) |
| 1065 | mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD); |
| 1066 | } |
| 1067 | |
| 1068 | /** |
| 1069 | * Update this BSSID cache |
| 1070 | * |
| 1071 | * @param rssi is the RSSI |
| 1072 | * @param value is the new instant loss value at this RSSI |
| 1073 | * @param volume is the volume for this single update |
| 1074 | */ |
| 1075 | public void updateLoss(int rssi, double value, int volume) { |
| 1076 | if (volume <= 0) return; |
| 1077 | int index = rssi - mRssiBase; |
| 1078 | if (index < 0 || index >= mEntriesSize) return; |
| 1079 | mEntries[index].update(value, volume); |
| 1080 | if (DBG) { |
| 1081 | DecimalFormat df = new DecimalFormat("#.##"); |
| 1082 | logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100) |
| 1083 | + "% volume=" + df.format(mEntries[index].mVolume)); |
| 1084 | } |
| 1085 | } |
| 1086 | |
| 1087 | /** |
| 1088 | * Get preset loss if the cache has insufficient data, observed from experiments. |
| 1089 | * |
| 1090 | * @param rssi is the input RSSI |
| 1091 | * @return preset loss of the given RSSI |
| 1092 | */ |
| 1093 | public double presetLoss(int rssi) { |
| 1094 | if (rssi <= -90) return 1.0; |
| 1095 | if (rssi > 0) return 0.0; |
| 1096 | |
| 1097 | if (sPresetLoss == null) { |
| 1098 | // pre-calculate all preset losses only once, then reuse them |
| 1099 | final int size = 90; |
| 1100 | sPresetLoss = new double[size]; |
| 1101 | for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5); |
| 1102 | } |
| 1103 | return sPresetLoss[-rssi]; |
| 1104 | } |
| 1105 | |
| 1106 | /** |
| 1107 | * A poor link is detected, calculate a target RSSI to bring WiFi back. |
| 1108 | * |
| 1109 | * @param rssi is the current RSSI |
| 1110 | * @return true iff the current BSSID should be avoided |
| 1111 | */ |
| 1112 | public boolean poorLinkDetected(int rssi) { |
| 1113 | if (DBG) logd("Poor link detected, rssi=" + rssi); |
| 1114 | |
| 1115 | long now = SystemClock.elapsedRealtime(); |
| 1116 | long lastGood = now - mLastTimeGood; |
| 1117 | long lastPoor = now - mLastTimePoor; |
| 1118 | |
| 1119 | // reduce the difficulty of good link target if last avoidance was long time ago |
| 1120 | while (mGoodLinkTargetIndex > 0 |
| 1121 | && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS) |
| 1122 | mGoodLinkTargetIndex--; |
| 1123 | mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT; |
| 1124 | |
| 1125 | // scan for a target RSSI at which the link is good |
| 1126 | int from = rssi + GOOD_LINK_RSSI_RANGE_MIN; |
| 1127 | int to = rssi + GOOD_LINK_RSSI_RANGE_MAX; |
| 1128 | mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); |
| 1129 | mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM; |
| 1130 | if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++; |
| 1131 | |
| 1132 | // calculate max avoidance time to prevent avoiding forever |
| 1133 | int p = 0, pmax = MAX_AVOID_TIME.length - 1; |
| 1134 | while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++; |
| 1135 | long avoidMax = MAX_AVOID_TIME[p].TIME_MS; |
| 1136 | |
| 1137 | // don't avoid if max avoidance time is 0 (RSSI is super high) |
| 1138 | if (avoidMax <= 0) return false; |
| 1139 | |
| 1140 | // set max avoidance time, send poor link notification |
| 1141 | mBssidAvoidTimeMax = now + avoidMax; |
| 1142 | |
| 1143 | if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount |
| 1144 | + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax); |
| 1145 | |
| 1146 | return true; |
| 1147 | } |
| 1148 | |
| 1149 | /** |
| 1150 | * A new BSSID is connected, recalculate target RSSI threshold |
| 1151 | */ |
| 1152 | public void newLinkDetected() { |
| 1153 | // if this BSSID is currently being avoided, the reuse those values |
| 1154 | if (mBssidAvoidTimeMax > 0) { |
| 1155 | if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi |
| 1156 | + " count=" + mGoodLinkTargetCount); |
| 1157 | return; |
| 1158 | } |
| 1159 | |
| 1160 | // calculate a new RSSI threshold for new link verifying |
| 1161 | int from = BSSID_STAT_RANGE_LOW_DBM; |
| 1162 | int to = BSSID_STAT_RANGE_HIGH_DBM; |
| 1163 | mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); |
| 1164 | mGoodLinkTargetCount = 1; |
| 1165 | mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS; |
| 1166 | if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count=" |
| 1167 | + mGoodLinkTargetCount); |
| 1168 | } |
| 1169 | |
| 1170 | /** |
| 1171 | * Return the first RSSI within the range where loss[rssi] < threshold |
| 1172 | * |
| 1173 | * @param from start scanning from this RSSI |
| 1174 | * @param to stop scanning at this RSSI |
| 1175 | * @param threshold target threshold for scanning |
| 1176 | * @return target RSSI |
| 1177 | */ |
| 1178 | public int findRssiTarget(int from, int to, double threshold) { |
| 1179 | from -= mRssiBase; |
| 1180 | to -= mRssiBase; |
| 1181 | int emptyCount = 0; |
| 1182 | int d = from < to ? 1 : -1; |
| 1183 | for (int i = from; i != to; i += d) |
| 1184 | // don't use a data point if it volume is too small (statistically unreliable) |
| 1185 | if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) { |
| 1186 | emptyCount = 0; |
| 1187 | if (mEntries[i].mValue < threshold) { |
| 1188 | // scan target found |
| 1189 | int rssi = mRssiBase + i; |
| 1190 | if (DBG) { |
| 1191 | DecimalFormat df = new DecimalFormat("#.##"); |
| 1192 | logd("Scan target found: rssi=" + rssi + " threshold=" |
| 1193 | + df.format(threshold * 100) + "% value=" |
| 1194 | + df.format(mEntries[i].mValue * 100) + "% volume=" |
| 1195 | + df.format(mEntries[i].mVolume)); |
| 1196 | } |
| 1197 | return rssi; |
| 1198 | } |
| 1199 | } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) { |
| 1200 | // cache has insufficient data around this RSSI, use preset loss instead |
| 1201 | int rssi = mRssiBase + i; |
| 1202 | double lossPreset = presetLoss(rssi); |
| 1203 | if (lossPreset < threshold) { |
| 1204 | if (DBG) { |
| 1205 | DecimalFormat df = new DecimalFormat("#.##"); |
| 1206 | logd("Scan target found: rssi=" + rssi + " threshold=" |
| 1207 | + df.format(threshold * 100) + "% value=" |
| 1208 | + df.format(lossPreset * 100) + "% volume=preset"); |
| 1209 | } |
| 1210 | return rssi; |
| 1211 | } |
| 1212 | } |
| 1213 | |
| 1214 | return mRssiBase + to; |
| 1215 | } |
| 1216 | } |
| Isaac Levy | 654f509 | 2011-07-13 17:41:45 -0700 | [diff] [blame] | 1217 | } |