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