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