Home | History | Annotate | Download | only in wifi
      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