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