blob: eb47a4a1251fad8dc6b1b8602767e3720fe3cb48 [file] [log] [blame]
Isaac Levy654f5092011-07-13 17:41:45 -07001/*
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
17package android.net.wifi;
18
19import android.content.BroadcastReceiver;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.database.ContentObserver;
25import android.net.ConnectivityManager;
Irfan Sheriff07573b32012-01-27 21:00:19 -080026import android.net.LinkProperties;
Isaac Levy654f5092011-07-13 17:41:45 -070027import android.net.NetworkInfo;
Yuhao Zhengf6307822012-08-14 14:21:25 -070028import android.net.wifi.RssiPacketCountInfo;
Isaac Levy654f5092011-07-13 17:41:45 -070029import android.os.Message;
30import android.os.SystemClock;
31import android.provider.Settings;
Isaac Levyd7b3e6a2011-07-20 18:15:30 -070032import android.provider.Settings.Secure;
Robert Greenwalt9b2886e2011-08-31 11:46:42 -070033import android.util.Log;
Yuhao Zhengb33227d2012-07-20 10:55:17 -070034import android.util.LruCache;
Isaac Levy654f5092011-07-13 17:41:45 -070035
Irfan Sheriff6b66e9e2011-10-24 16:55:50 -070036import com.android.internal.R;
Irfan Sheriff07573b32012-01-27 21:00:19 -080037import com.android.internal.util.AsyncChannel;
Isaac Levy654f5092011-07-13 17:41:45 -070038import com.android.internal.util.Protocol;
39import com.android.internal.util.State;
40import com.android.internal.util.StateMachine;
41
Irfan Sherifff0afe412012-11-30 14:07:44 -080042import java.io.FileDescriptor;
Isaac Levy654f5092011-07-13 17:41:45 -070043import java.io.PrintWriter;
Yuhao Zhengb33227d2012-07-20 10:55:17 -070044import java.text.DecimalFormat;
Isaac Levy654f5092011-07-13 17:41:45 -070045
46/**
Yuhao Zhengb33227d2012-07-20 10:55:17 -070047 * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi
48 * connects at L2 layer, the beacons from access point reach the device and it
49 * can maintain a connection, but the application connectivity can be flaky (due
50 * to bigger packet size exchange).
51 * <p>
52 * We now monitor the quality of the last hop on WiFi using packet loss ratio as
53 * an indicator to decide if the link is good enough to switch to Wi-Fi as the
54 * uplink.
55 * <p>
56 * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the
57 * instant packet loss, and record it as per-AP loss-to-rssi statistics. When
58 * the instant packet loss is higher than a threshold, the WiFi watchdog sends a
59 * poor link notification to avoid WiFi connection temporarily.
60 * <p>
61 * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to
62 * bring the WiFi connection back. Once the RSSI is high enough to achieve a
63 * lower packet loss, a good link detection is sent such that the WiFi
64 * connection become available again.
65 * <p>
66 * BSSID roaming has been taken into account. When user is moving across
67 * multiple APs, the WiFi watchdog will detect that and keep watching the
68 * currently connected AP.
69 * <p>
70 * Power impact should be minimal since much of the measurement relies on
71 * passive statistics already being tracked at the driver and the polling is
72 * done when screen is turned on and the RSSI is in a certain range.
Isaac Levy654f5092011-07-13 17:41:45 -070073 *
74 * @hide
75 */
76public class WifiWatchdogStateMachine extends StateMachine {
77
Irfan Sheriff07573b32012-01-27 21:00:19 -080078 /* STOPSHIP: Keep this configurable for debugging until ship */
79 private static boolean DBG = false;
Yuhao Zhengb33227d2012-07-20 10:55:17 -070080
81 private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
82
83 /* Internal events */
84 private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
85 private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
86 private static final int EVENT_RSSI_CHANGE = BASE + 3;
87 private static final int EVENT_SUPPLICANT_STATE_CHANGE = BASE + 4;
88 private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
89 private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6;
90 private static final int EVENT_BSSID_CHANGE = BASE + 7;
91 private static final int EVENT_SCREEN_ON = BASE + 8;
92 private static final int EVENT_SCREEN_OFF = BASE + 9;
93
94 /* Internal messages */
Irfan Sheriffda6da092012-08-16 12:49:23 -070095 private static final int CMD_RSSI_FETCH = BASE + 11;
Yuhao Zhengb33227d2012-07-20 10:55:17 -070096
97 /* Notifications from/to WifiStateMachine */
98 static final int POOR_LINK_DETECTED = BASE + 21;
99 static final int GOOD_LINK_DETECTED = BASE + 22;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700100
Irfan Sheriff17cf1f22012-10-09 17:08:39 -0700101 public static final boolean DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED = false;
102
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700103 /*
104 * RSSI levels as used by notification icon
105 * Level 4 -55 <= RSSI
106 * Level 3 -66 <= RSSI < -55
107 * Level 2 -77 <= RSSI < -67
108 * Level 1 -88 <= RSSI < -78
109 * Level 0 RSSI < -88
110 */
111
112 /**
113 * WiFi link statistics is monitored and recorded actively below this threshold.
114 * <p>
115 * Larger threshold is more adaptive but increases sampling cost.
116 */
Yuhao Zhengf6307822012-08-14 14:21:25 -0700117 private static final int LINK_MONITOR_LEVEL_THRESHOLD = WifiManager.RSSI_LEVELS - 1;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700118
119 /**
120 * Remember packet loss statistics of how many BSSIDs.
121 * <p>
122 * Larger size is usually better but requires more space.
123 */
124 private static final int BSSID_STAT_CACHE_SIZE = 20;
125
126 /**
127 * RSSI range of a BSSID statistics.
128 * Within the range, (RSSI -> packet loss %) mappings are stored.
129 * <p>
130 * Larger range is usually better but requires more space.
131 */
132 private static final int BSSID_STAT_RANGE_LOW_DBM = -105;
133
134 /**
135 * See {@link #BSSID_STAT_RANGE_LOW_DBM}.
136 */
137 private static final int BSSID_STAT_RANGE_HIGH_DBM = -45;
138
139 /**
140 * How many consecutive empty data point to trigger a empty-cache detection.
141 * In this case, a preset/default loss value (function on RSSI) is used.
142 * <p>
143 * In normal uses, some RSSI values may never be seen due to channel randomness.
144 * However, the size of such empty RSSI chunk in normal use is generally 1~2.
145 */
146 private static final int BSSID_STAT_EMPTY_COUNT = 3;
147
148 /**
149 * Sample interval for packet loss statistics, in msec.
150 * <p>
151 * Smaller interval is more accurate but increases sampling cost (battery consumption).
152 */
153 private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000;
154
155 /**
156 * Coefficients (alpha) for moving average for packet loss tracking.
157 * Must be within (0.0, 1.0).
158 * <p>
159 * Equivalent number of samples: N = 2 / alpha - 1 .
160 * We want the historic loss to base on more data points to be statistically reliable.
161 * We want the current instant loss to base on less data points to be responsive.
162 */
163 private static final double EXP_COEFFICIENT_RECORD = 0.1;
164
165 /**
166 * See {@link #EXP_COEFFICIENT_RECORD}.
167 */
168 private static final double EXP_COEFFICIENT_MONITOR = 0.5;
169
170 /**
171 * Thresholds for sending good/poor link notifications, in packet loss %.
172 * Good threshold must be smaller than poor threshold.
173 * Use smaller poor threshold to avoid WiFi more aggressively.
174 * Use smaller good threshold to bring back WiFi more conservatively.
175 * <p>
176 * When approaching the boundary, loss ratio jumps significantly within a few dBs.
177 * 50% loss threshold is a good balance between accuracy and reponsiveness.
178 * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily.
179 */
180 private static final double POOR_LINK_LOSS_THRESHOLD = 0.5;
181
182 /**
183 * See {@link #POOR_LINK_LOSS_THRESHOLD}.
184 */
185 private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1;
186
187 /**
188 * Number of samples to confirm before sending a poor link notification.
189 * Response time = confirm_count * sample_interval .
190 * <p>
191 * A smaller threshold improves response speed but may suffer from randomness.
192 * According to experiments, 3~5 are good values to achieve a balance.
193 * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}.
194 */
195 private static final int POOR_LINK_SAMPLE_COUNT = 3;
196
197 /**
198 * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness.
199 * <p>
200 * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive.
201 */
202 private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0;
203
204 /**
205 * When a poor link is detected, we scan over this range (based on current
206 * poor link RSSI) for a target RSSI that satisfies a target packet loss.
207 * Refer to {@link #GOOD_LINK_TARGET}.
208 * <p>
209 * We want range_min not too small to avoid jumping back to WiFi too easily.
210 */
211 private static final int GOOD_LINK_RSSI_RANGE_MIN = 3;
212
213 /**
214 * See {@link #GOOD_LINK_RSSI_RANGE_MIN}.
215 */
216 private static final int GOOD_LINK_RSSI_RANGE_MAX = 20;
217
218 /**
219 * Adaptive good link target to avoid flapping.
220 * When a poor link is detected, a good link target is calculated as follows:
221 * <p>
Yuhao Zhengf6307822012-08-14 14:21:25 -0700222 * targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i],
223 * where rssi is within the above GOOD_LINK_RSSI_RANGE.
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700224 * targetCount = sample_count[i] .
225 * <p>
226 * While WiFi is being avoided, we keep monitoring its signal strength.
227 * Good link notification is sent when we see current RSSI >= targetRSSI
228 * for targetCount consecutive times.
229 * <p>
230 * Index i is incremented each time after a poor link detection.
231 * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago.
232 * <p>
233 * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping.
234 * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve.
Yuhao Zhengf6307822012-08-14 14:21:25 -0700235 * Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago).
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700236 */
237 private static final GoodLinkTarget[] GOOD_LINK_TARGET = {
238 /* rssi_adj, sample_count, reduce_time */
239 new GoodLinkTarget( 0, 3, 30 * 60000 ),
240 new GoodLinkTarget( 3, 5, 5 * 60000 ),
241 new GoodLinkTarget( 6, 10, 1 * 60000 ),
242 new GoodLinkTarget( 9, 30, 0 * 60000 ),
243 };
244
245 /**
246 * The max time to avoid a BSSID, to prevent avoiding forever.
247 * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i]
248 * <p>
249 * It is unusual to experience high packet loss at high RSSI. Something unusual must be
250 * happening (e.g. strong interference). For higher signal strengths, we set the avoidance
251 * time to be low to allow for quick turn around from temporary interference.
252 * <p>
253 * See {@link BssidStatistics#poorLinkDetected}.
254 */
255 private static final MaxAvoidTime[] MAX_AVOID_TIME = {
256 /* max_time, min_rssi */
257 new MaxAvoidTime( 30 * 60000, -200 ),
258 new MaxAvoidTime( 5 * 60000, -70 ),
259 new MaxAvoidTime( 0 * 60000, -55 ),
260 };
261
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700262 /* Framework related */
Isaac Levy654f5092011-07-13 17:41:45 -0700263 private Context mContext;
264 private ContentResolver mContentResolver;
265 private WifiManager mWifiManager;
Isaac Levy654f5092011-07-13 17:41:45 -0700266 private IntentFilter mIntentFilter;
267 private BroadcastReceiver mBroadcastReceiver;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700268 private AsyncChannel mWsmChannel = new AsyncChannel();
269 private WifiInfo mWifiInfo;
270 private LinkProperties mLinkProperties;
Isaac Levy654f5092011-07-13 17:41:45 -0700271
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700272 /* System settingss related */
273 private static boolean sWifiOnly = false;
274 private boolean mPoorNetworkDetectionEnabled;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700275
276 /* Poor link detection related */
277 private LruCache<String, BssidStatistics> mBssidCache =
278 new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE);
279 private int mRssiFetchToken = 0;
280 private int mCurrentSignalLevel;
281 private BssidStatistics mCurrentBssid;
282 private VolumeWeightedEMA mCurrentLoss;
283 private boolean mIsScreenOn = true;
284 private static double sPresetLoss[];
285
286 /* WiFi watchdog state machine related */
Isaac Levy654f5092011-07-13 17:41:45 -0700287 private DefaultState mDefaultState = new DefaultState();
288 private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
289 private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
290 private NotConnectedState mNotConnectedState = new NotConnectedState();
Irfan Sheriff07573b32012-01-27 21:00:19 -0800291 private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
Isaac Levy654f5092011-07-13 17:41:45 -0700292 private ConnectedState mConnectedState = new ConnectedState();
Isaac Levy654f5092011-07-13 17:41:45 -0700293 private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700294 private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState();
Irfan Sheriff19380da2011-12-06 14:41:14 -0800295 private OnlineState mOnlineState = new OnlineState();
Isaac Levy654f5092011-07-13 17:41:45 -0700296
Isaac Levy654f5092011-07-13 17:41:45 -0700297 /**
298 * STATE MAP
299 * Default
300 * / \
Irfan Sheriff07573b32012-01-27 21:00:19 -0800301 * Disabled Enabled
302 * / \ \
303 * NotConnected Verifying Connected
304 * /---------\
305 * (all other states)
Isaac Levy654f5092011-07-13 17:41:45 -0700306 */
307 private WifiWatchdogStateMachine(Context context) {
Wink Saville58c73c32013-01-28 10:52:34 -0800308 super("WifiWatchdogStateMachine");
Isaac Levy654f5092011-07-13 17:41:45 -0700309 mContext = context;
310 mContentResolver = context.getContentResolver();
311 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800312 mWsmChannel.connectSync(mContext, getHandler(),
313 mWifiManager.getWifiStateMachineMessenger());
Isaac Levy654f5092011-07-13 17:41:45 -0700314
315 setupNetworkReceiver();
316
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700317 // the content observer to listen needs a handler
Isaac Levy654f5092011-07-13 17:41:45 -0700318 registerForSettingsChanges();
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700319 registerForWatchdogToggle();
Isaac Levy654f5092011-07-13 17:41:45 -0700320 addState(mDefaultState);
321 addState(mWatchdogDisabledState, mDefaultState);
322 addState(mWatchdogEnabledState, mDefaultState);
323 addState(mNotConnectedState, mWatchdogEnabledState);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800324 addState(mVerifyingLinkState, mWatchdogEnabledState);
Isaac Levy654f5092011-07-13 17:41:45 -0700325 addState(mConnectedState, mWatchdogEnabledState);
Isaac Levy654f5092011-07-13 17:41:45 -0700326 addState(mOnlineWatchState, mConnectedState);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700327 addState(mLinkMonitoringState, mConnectedState);
Irfan Sheriff19380da2011-12-06 14:41:14 -0800328 addState(mOnlineState, mConnectedState);
Isaac Levy654f5092011-07-13 17:41:45 -0700329
Irfan Sheriff07573b32012-01-27 21:00:19 -0800330 if (isWatchdogEnabled()) {
331 setInitialState(mNotConnectedState);
332 } else {
333 setInitialState(mWatchdogDisabledState);
334 }
Irfan Sherifff0afe412012-11-30 14:07:44 -0800335 setLogRecSize(25);
336 setLogOnlyTransitions(true);
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700337 updateSettings();
Isaac Levy654f5092011-07-13 17:41:45 -0700338 }
339
340 public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
Isaac Levy4ad39d62011-07-26 18:59:12 -0700341 ContentResolver contentResolver = context.getContentResolver();
Robert Greenwalt9b2886e2011-08-31 11:46:42 -0700342
343 ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
344 Context.CONNECTIVITY_SERVICE);
345 sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
346
Irfan Sheriffda6da092012-08-16 12:49:23 -0700347 // Watchdog is always enabled. Poor network detection can be seperately turned on/off
Irfan Sheriffae094b22012-05-30 16:52:38 -0700348 // TODO: Remove this setting & clean up state machine since we always have
349 // watchdog in an enabled state
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700350 putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
Irfan Sheriffae094b22012-05-30 16:52:38 -0700351
Isaac Levy654f5092011-07-13 17:41:45 -0700352 WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
353 wwsm.start();
Isaac Levy654f5092011-07-13 17:41:45 -0700354 return wwsm;
355 }
356
Isaac Levy654f5092011-07-13 17:41:45 -0700357 private void setupNetworkReceiver() {
358 mBroadcastReceiver = new BroadcastReceiver() {
359 @Override
360 public void onReceive(Context context, Intent intent) {
361 String action = intent.getAction();
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700362 if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
Irfan Sheriff07573b32012-01-27 21:00:19 -0800363 obtainMessage(EVENT_RSSI_CHANGE,
364 intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700365 } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
366 sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent);
367 } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
368 sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
369 } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
370 sendMessage(EVENT_SCREEN_ON);
371 } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
372 sendMessage(EVENT_SCREEN_OFF);
Isaac Levy654f5092011-07-13 17:41:45 -0700373 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700374 sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra(
375 WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
Isaac Levy654f5092011-07-13 17:41:45 -0700376 }
377 }
378 };
379
380 mIntentFilter = new IntentFilter();
381 mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
382 mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
383 mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700384 mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
385 mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
386 mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800387 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
Isaac Levy654f5092011-07-13 17:41:45 -0700388 }
389
390 /**
391 * Observes the watchdog on/off setting, and takes action when changed.
392 */
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700393 private void registerForWatchdogToggle() {
Isaac Levy654f5092011-07-13 17:41:45 -0700394 ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
395 @Override
396 public void onChange(boolean selfChange) {
397 sendMessage(EVENT_WATCHDOG_TOGGLED);
398 }
399 };
400
401 mContext.getContentResolver().registerContentObserver(
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700402 Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_ON),
Isaac Levy654f5092011-07-13 17:41:45 -0700403 false, contentObserver);
404 }
405
406 /**
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700407 * Observes watchdogs secure setting changes.
408 */
409 private void registerForSettingsChanges() {
410 ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
411 @Override
412 public void onChange(boolean selfChange) {
413 sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
414 }
415 };
416
417 mContext.getContentResolver().registerContentObserver(
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700418 Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700419 false, contentObserver);
Isaac Levy654f5092011-07-13 17:41:45 -0700420 }
421
Irfan Sherifff0afe412012-11-30 14:07:44 -0800422 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
423 super.dump(fd, pw, args);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800424 pw.println("mWifiInfo: [" + mWifiInfo + "]");
425 pw.println("mLinkProperties: [" + mLinkProperties + "]");
426 pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
Irfan Sheriff07573b32012-01-27 21:00:19 -0800427 pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
Isaac Levy654f5092011-07-13 17:41:45 -0700428 }
429
Isaac Levy654f5092011-07-13 17:41:45 -0700430 private boolean isWatchdogEnabled() {
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700431 boolean ret = getSettingsGlobalBoolean(
432 mContentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700433 if (DBG) logd("Watchdog enabled " + ret);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800434 return ret;
Isaac Levy654f5092011-07-13 17:41:45 -0700435 }
436
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700437 private void updateSettings() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700438 if (DBG) logd("Updating secure settings");
Irfan Sheriff07573b32012-01-27 21:00:19 -0800439
Irfan Sheriffbbe461b2012-10-09 15:49:33 -0700440 // disable poor network avoidance
441 if (sWifiOnly) {
442 logd("Disabling poor network avoidance for wi-fi only device");
443 mPoorNetworkDetectionEnabled = false;
444 } else {
445 mPoorNetworkDetectionEnabled = getSettingsGlobalBoolean(mContentResolver,
Irfan Sheriff17cf1f22012-10-09 17:08:39 -0700446 Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
447 DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED);
Irfan Sheriffbbe461b2012-10-09 15:49:33 -0700448 }
Irfan Sheriff6b66e9e2011-10-24 16:55:50 -0700449 }
450
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700451 /**
452 * Default state, guard for unhandled messages.
453 */
Isaac Levy654f5092011-07-13 17:41:45 -0700454 class DefaultState extends State {
455 @Override
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800456 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700457 if (DBG) logd(getName());
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800458 }
459
460 @Override
Isaac Levy654f5092011-07-13 17:41:45 -0700461 public boolean processMessage(Message msg) {
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700462 switch (msg.what) {
463 case EVENT_WATCHDOG_SETTINGS_CHANGE:
464 updateSettings();
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700465 if (DBG) logd("Updating wifi-watchdog secure settings");
Irfan Sheriff07573b32012-01-27 21:00:19 -0800466 break;
467 case EVENT_RSSI_CHANGE:
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800468 mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800469 break;
470 case EVENT_WIFI_RADIO_STATE_CHANGE:
471 case EVENT_NETWORK_STATE_CHANGE:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700472 case EVENT_SUPPLICANT_STATE_CHANGE:
473 case EVENT_BSSID_CHANGE:
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800474 case CMD_RSSI_FETCH:
Yuhao Zhengf6307822012-08-14 14:21:25 -0700475 case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
476 case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700477 // ignore
478 break;
479 case EVENT_SCREEN_ON:
480 mIsScreenOn = true;
481 break;
482 case EVENT_SCREEN_OFF:
483 mIsScreenOn = false;
Irfan Sheriff07573b32012-01-27 21:00:19 -0800484 break;
485 default:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700486 loge("Unhandled message " + msg + " in state " + getCurrentState().getName());
Irfan Sheriff07573b32012-01-27 21:00:19 -0800487 break;
Isaac Levy654f5092011-07-13 17:41:45 -0700488 }
489 return HANDLED;
490 }
491 }
492
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700493 /**
494 * WiFi watchdog is disabled by the setting.
495 */
Isaac Levy654f5092011-07-13 17:41:45 -0700496 class WatchdogDisabledState extends State {
497 @Override
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800498 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700499 if (DBG) logd(getName());
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800500 }
501
502 @Override
Isaac Levy654f5092011-07-13 17:41:45 -0700503 public boolean processMessage(Message msg) {
504 switch (msg.what) {
505 case EVENT_WATCHDOG_TOGGLED:
506 if (isWatchdogEnabled())
507 transitionTo(mNotConnectedState);
508 return HANDLED;
Irfan Sheriff07573b32012-01-27 21:00:19 -0800509 case EVENT_NETWORK_STATE_CHANGE:
510 Intent intent = (Intent) msg.obj;
511 NetworkInfo networkInfo = (NetworkInfo)
512 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
513
514 switch (networkInfo.getDetailedState()) {
515 case VERIFYING_POOR_LINK:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700516 if (DBG) logd("Watchdog disabled, verify link");
517 sendLinkStatusNotification(true);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800518 break;
519 default:
520 break;
521 }
522 break;
Isaac Levy654f5092011-07-13 17:41:45 -0700523 }
524 return NOT_HANDLED;
525 }
526 }
527
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700528 /**
529 * WiFi watchdog is enabled by the setting.
530 */
Isaac Levy654f5092011-07-13 17:41:45 -0700531 class WatchdogEnabledState extends State {
532 @Override
533 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700534 if (DBG) logd(getName());
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800535 }
Isaac Levy654f5092011-07-13 17:41:45 -0700536
537 @Override
538 public boolean processMessage(Message msg) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700539 Intent intent;
Isaac Levy654f5092011-07-13 17:41:45 -0700540 switch (msg.what) {
541 case EVENT_WATCHDOG_TOGGLED:
542 if (!isWatchdogEnabled())
543 transitionTo(mWatchdogDisabledState);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800544 break;
Isaac Levy654f5092011-07-13 17:41:45 -0700545
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700546 case EVENT_NETWORK_STATE_CHANGE:
547 intent = (Intent) msg.obj;
548 NetworkInfo networkInfo =
549 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
550 if (DBG) logd("Network state change " + networkInfo.getDetailedState());
551
552 mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
553 updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800554
Irfan Sheriff07573b32012-01-27 21:00:19 -0800555 switch (networkInfo.getDetailedState()) {
556 case VERIFYING_POOR_LINK:
557 mLinkProperties = (LinkProperties) intent.getParcelableExtra(
558 WifiManager.EXTRA_LINK_PROPERTIES);
Irfan Sheriff19380da2011-12-06 14:41:14 -0800559 if (mPoorNetworkDetectionEnabled) {
Irfan Sheriffedba8522012-10-16 18:10:38 -0700560 if (mWifiInfo == null || mCurrentBssid == null) {
561 loge("Ignore, wifiinfo " + mWifiInfo +" bssid " + mCurrentBssid);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700562 sendLinkStatusNotification(true);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800563 } else {
564 transitionTo(mVerifyingLinkState);
565 }
Irfan Sheriff19380da2011-12-06 14:41:14 -0800566 } else {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700567 sendLinkStatusNotification(true);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800568 }
569 break;
570 case CONNECTED:
Irfan Sheriffda6da092012-08-16 12:49:23 -0700571 transitionTo(mOnlineWatchState);
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700572 break;
573 default:
Isaac Levy654f5092011-07-13 17:41:45 -0700574 transitionTo(mNotConnectedState);
Irfan Sheriff32f04e92011-09-23 14:48:26 -0700575 break;
Isaac Levy654f5092011-07-13 17:41:45 -0700576 }
Irfan Sheriff07573b32012-01-27 21:00:19 -0800577 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700578
579 case EVENT_SUPPLICANT_STATE_CHANGE:
580 intent = (Intent) msg.obj;
581 SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra(
582 WifiManager.EXTRA_NEW_STATE);
583 if (supplicantState == SupplicantState.COMPLETED) {
584 mWifiInfo = mWifiManager.getConnectionInfo();
585 updateCurrentBssid(mWifiInfo.getBSSID());
Isaac Levy654f5092011-07-13 17:41:45 -0700586 }
Irfan Sheriff07573b32012-01-27 21:00:19 -0800587 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700588
589 case EVENT_WIFI_RADIO_STATE_CHANGE:
Irfan Sheriffe6db5002013-03-14 14:29:26 -0700590 if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700591 transitionTo(mNotConnectedState);
Irfan Sheriffe6db5002013-03-14 14:29:26 -0700592 }
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700593 break;
594
Irfan Sheriff07573b32012-01-27 21:00:19 -0800595 default:
596 return NOT_HANDLED;
Isaac Levy654f5092011-07-13 17:41:45 -0700597 }
598
Irfan Sheriff07573b32012-01-27 21:00:19 -0800599 return HANDLED;
Isaac Levy654f5092011-07-13 17:41:45 -0700600 }
Isaac Levy654f5092011-07-13 17:41:45 -0700601 }
602
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700603 /**
604 * WiFi is disconnected.
605 */
Isaac Levy654f5092011-07-13 17:41:45 -0700606 class NotConnectedState extends State {
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800607 @Override
608 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700609 if (DBG) logd(getName());
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800610 }
Isaac Levy654f5092011-07-13 17:41:45 -0700611 }
612
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700613 /**
614 * WiFi is connected, but waiting for good link detection message.
615 */
Irfan Sheriff07573b32012-01-27 21:00:19 -0800616 class VerifyingLinkState extends State {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700617
618 private int mSampleCount;
619
Irfan Sheriff07573b32012-01-27 21:00:19 -0800620 @Override
621 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700622 if (DBG) logd(getName());
623 mSampleCount = 0;
624 mCurrentBssid.newLinkDetected();
625 sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
Irfan Sheriff07573b32012-01-27 21:00:19 -0800626 }
627
Isaac Levy654f5092011-07-13 17:41:45 -0700628 @Override
629 public boolean processMessage(Message msg) {
630 switch (msg.what) {
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700631 case EVENT_WATCHDOG_SETTINGS_CHANGE:
Irfan Sheriff19380da2011-12-06 14:41:14 -0800632 updateSettings();
Irfan Sheriff07573b32012-01-27 21:00:19 -0800633 if (!mPoorNetworkDetectionEnabled) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700634 sendLinkStatusNotification(true);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800635 }
636 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700637
638 case EVENT_BSSID_CHANGE:
639 transitionTo(mVerifyingLinkState);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800640 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700641
642 case CMD_RSSI_FETCH:
643 if (msg.arg1 == mRssiFetchToken) {
Yuhao Zhengf6307822012-08-14 14:21:25 -0700644 mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700645 sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
646 LINK_SAMPLING_INTERVAL_MS);
647 }
648 break;
649
Yuhao Zhengf6307822012-08-14 14:21:25 -0700650 case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
651 RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
652 int rssi = info.rssi;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700653 if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi);
654
655 long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime();
656 if (time <= 0) {
657 // max avoidance time is met
658 if (DBG) logd("Max avoid time elapsed");
659 sendLinkStatusNotification(true);
660 } else {
661 if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) {
662 if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) {
663 // link is good again
664 if (DBG) logd("Good link detected, rssi=" + rssi);
665 mCurrentBssid.mBssidAvoidTimeMax = 0;
666 sendLinkStatusNotification(true);
667 }
Irfan Sheriff07573b32012-01-27 21:00:19 -0800668 } else {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700669 mSampleCount = 0;
670 if (DBG) logd("Link is still poor, time left=" + time);
Irfan Sheriff07573b32012-01-27 21:00:19 -0800671 }
672 }
673 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700674
Yuhao Zhengf6307822012-08-14 14:21:25 -0700675 case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700676 if (DBG) logd("RSSI_FETCH_FAILED");
677 break;
678
Irfan Sheriff07573b32012-01-27 21:00:19 -0800679 default:
680 return NOT_HANDLED;
681 }
682 return HANDLED;
683 }
684 }
685
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700686 /**
687 * WiFi is connected and link is verified.
688 */
Irfan Sheriff07573b32012-01-27 21:00:19 -0800689 class ConnectedState extends State {
690 @Override
691 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700692 if (DBG) logd(getName());
Irfan Sheriff07573b32012-01-27 21:00:19 -0800693 }
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700694
Irfan Sheriff07573b32012-01-27 21:00:19 -0800695 @Override
696 public boolean processMessage(Message msg) {
697 switch (msg.what) {
698 case EVENT_WATCHDOG_SETTINGS_CHANGE:
699 updateSettings();
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700700 // STOPSHIP: Remove this at ship
701 logd("Updated secure settings and turned debug on");
Irfan Sheriff07573b32012-01-27 21:00:19 -0800702 DBG = true;
Irfan Sheriff07573b32012-01-27 21:00:19 -0800703
Irfan Sheriff19380da2011-12-06 14:41:14 -0800704 if (mPoorNetworkDetectionEnabled) {
705 transitionTo(mOnlineWatchState);
706 } else {
707 transitionTo(mOnlineState);
708 }
709 return HANDLED;
Isaac Levy654f5092011-07-13 17:41:45 -0700710 }
711 return NOT_HANDLED;
712 }
Isaac Levy654f5092011-07-13 17:41:45 -0700713 }
714
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700715 /**
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700716 * RSSI is high enough and don't need link monitoring.
717 */
Isaac Levy654f5092011-07-13 17:41:45 -0700718 class OnlineWatchState extends State {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700719 @Override
Isaac Levy654f5092011-07-13 17:41:45 -0700720 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700721 if (DBG) logd(getName());
Irfan Sheriff07573b32012-01-27 21:00:19 -0800722 if (mPoorNetworkDetectionEnabled) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700723 // treat entry as an rssi change
Irfan Sheriff07573b32012-01-27 21:00:19 -0800724 handleRssiChange();
725 } else {
726 transitionTo(mOnlineState);
727 }
728 }
729
730 private void handleRssiChange() {
Irfan Sheriffedba8522012-10-16 18:10:38 -0700731 if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD && mCurrentBssid != null) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700732 transitionTo(mLinkMonitoringState);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800733 } else {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700734 // stay here
Irfan Sheriff07573b32012-01-27 21:00:19 -0800735 }
Isaac Levy654f5092011-07-13 17:41:45 -0700736 }
737
738 @Override
739 public boolean processMessage(Message msg) {
740 switch (msg.what) {
741 case EVENT_RSSI_CHANGE:
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800742 mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700743 handleRssiChange();
Irfan Sheriff07573b32012-01-27 21:00:19 -0800744 break;
745 default:
746 return NOT_HANDLED;
Isaac Levy654f5092011-07-13 17:41:45 -0700747 }
Irfan Sheriff07573b32012-01-27 21:00:19 -0800748 return HANDLED;
Isaac Levy654f5092011-07-13 17:41:45 -0700749 }
750 }
751
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700752 /**
753 * Keep sampling the link and monitor any poor link situation.
754 */
755 class LinkMonitoringState extends State {
756
757 private int mSampleCount;
758
759 private int mLastRssi;
760 private int mLastTxGood;
761 private int mLastTxBad;
762
763 @Override
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800764 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700765 if (DBG) logd(getName());
766 mSampleCount = 0;
767 mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800768 sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
769 }
770
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700771 @Override
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800772 public boolean processMessage(Message msg) {
773 switch (msg.what) {
774 case EVENT_RSSI_CHANGE:
775 mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700776 if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) {
777 // stay here;
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800778 } else {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700779 // we don't need frequent RSSI monitoring any more
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800780 transitionTo(mOnlineWatchState);
781 }
782 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700783
784 case EVENT_BSSID_CHANGE:
785 transitionTo(mLinkMonitoringState);
786 break;
787
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800788 case CMD_RSSI_FETCH:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700789 if (!mIsScreenOn) {
790 transitionTo(mOnlineState);
791 } else if (msg.arg1 == mRssiFetchToken) {
Yuhao Zhengf6307822012-08-14 14:21:25 -0700792 mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800793 sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700794 LINK_SAMPLING_INTERVAL_MS);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800795 }
796 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700797
Yuhao Zhengf6307822012-08-14 14:21:25 -0700798 case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
799 RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
800 int rssi = info.rssi;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700801 int mrssi = (mLastRssi + rssi) / 2;
Yuhao Zhengf6307822012-08-14 14:21:25 -0700802 int txbad = info.txbad;
803 int txgood = info.txgood;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700804 if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad="
805 + txbad + " txgood=" + txgood);
806
807 // skip the first data point as we want incremental values
808 long now = SystemClock.elapsedRealtime();
809 if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) {
810
811 // update packet loss statistics
812 int dbad = txbad - mLastTxBad;
813 int dgood = txgood - mLastTxGood;
814 int dtotal = dbad + dgood;
815
816 if (dtotal > 0) {
817 // calculate packet loss in the last sampling interval
818 double loss = ((double) dbad) / ((double) dtotal);
819
820 mCurrentLoss.update(loss, dtotal);
821
822 if (DBG) {
823 DecimalFormat df = new DecimalFormat("#.##");
824 logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss="
825 + df.format(mCurrentLoss.mValue * 100) + "% volume="
826 + df.format(mCurrentLoss.mVolume));
827 }
828
829 mCurrentBssid.updateLoss(mrssi, loss, dtotal);
830
831 // check for high packet loss and send poor link notification
832 if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD
833 && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) {
834 if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT)
835 if (mCurrentBssid.poorLinkDetected(rssi)) {
836 sendLinkStatusNotification(false);
837 ++mRssiFetchToken;
838 }
839 } else {
840 mSampleCount = 0;
841 }
842 }
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800843 }
844
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700845 mCurrentBssid.mLastTimeSample = now;
846 mLastTxBad = txbad;
847 mLastTxGood = txgood;
848 mLastRssi = rssi;
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800849 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700850
Yuhao Zhengf6307822012-08-14 14:21:25 -0700851 case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700852 // can happen if we are waiting to get a disconnect notification
853 if (DBG) logd("RSSI_FETCH_FAILED");
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800854 break;
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700855
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800856 default:
857 return NOT_HANDLED;
858 }
859 return HANDLED;
860 }
861 }
862
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700863 /**
864 * Child state of ConnectedState indicating that we are online and there is nothing to do.
Irfan Sheriff19380da2011-12-06 14:41:14 -0800865 */
866 class OnlineState extends State {
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800867 @Override
868 public void enter() {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700869 if (DBG) logd(getName());
870 }
871
872 @Override
873 public boolean processMessage(Message msg) {
874 switch (msg.what) {
875 case EVENT_SCREEN_ON:
876 mIsScreenOn = true;
877 if (mPoorNetworkDetectionEnabled)
878 transitionTo(mOnlineWatchState);
879 break;
880 default:
881 return NOT_HANDLED;
882 }
883 return HANDLED;
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800884 }
Irfan Sheriff19380da2011-12-06 14:41:14 -0800885 }
886
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700887 private void updateCurrentBssid(String bssid) {
888 if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));
889
890 // if currently not connected, then set current BSSID to null
891 if (bssid == null) {
892 if (mCurrentBssid == null) return;
893 mCurrentBssid = null;
894 if (DBG) logd("BSSID changed");
895 sendMessage(EVENT_BSSID_CHANGE);
896 return;
897 }
898
899 // if it is already the current BSSID, then done
900 if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return;
901
902 // search for the new BSSID in the cache, add to cache if not found
903 mCurrentBssid = mBssidCache.get(bssid);
904 if (mCurrentBssid == null) {
905 mCurrentBssid = new BssidStatistics(bssid);
906 mBssidCache.put(bssid, mCurrentBssid);
907 }
908
909 // send BSSID change notification
910 if (DBG) logd("BSSID changed");
911 sendMessage(EVENT_BSSID_CHANGE);
912 }
913
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800914 private int calculateSignalLevel(int rssi) {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700915 int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
916 if (DBG)
917 logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel);
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800918 return signalLevel;
919 }
920
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700921 private void sendLinkStatusNotification(boolean isGood) {
922 if (DBG) logd("########################################");
923 if (isGood) {
924 mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
Irfan Sheriffedba8522012-10-16 18:10:38 -0700925 if (mCurrentBssid != null) {
926 mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime();
927 }
928 if (DBG) logd("Good link notification is sent");
Irfan Sheriff90d57df2012-03-12 15:55:20 -0700929 } else {
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700930 mWsmChannel.sendMessage(POOR_LINK_DETECTED);
Irfan Sheriffedba8522012-10-16 18:10:38 -0700931 if (mCurrentBssid != null) {
932 mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime();
933 }
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700934 logd("Poor link notification is sent");
Irfan Sheriff90d57df2012-03-12 15:55:20 -0700935 }
Irfan Sheriff4c8982a2012-03-08 16:11:02 -0800936 }
937
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700938 /**
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700939 * Convenience function for retrieving a single secure settings value as a
940 * boolean. Note that internally setting values are always stored as
941 * strings; this function converts the string to a boolean for you. The
942 * default value will be returned if the setting is not defined or not a
943 * valid boolean.
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700944 *
945 * @param cr The ContentResolver to access.
946 * @param name The name of the setting to retrieve.
947 * @param def Value to return if the setting is not defined.
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700948 * @return The setting's current value, or 'def' if it is not defined or not
949 * a valid boolean.
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700950 */
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700951 private static boolean getSettingsGlobalBoolean(ContentResolver cr, String name, boolean def) {
952 return Settings.Global.getInt(cr, name, def ? 1 : 0) == 1;
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700953 }
954
955 /**
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700956 * Convenience function for updating a single settings value as an integer.
957 * This will either create a new entry in the table if the given name does
958 * not exist, or modify the value of the existing row with that name. Note
959 * that internally setting values are always stored as strings, so this
960 * function converts the given value to a string before storing it.
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700961 *
962 * @param cr The ContentResolver to access.
963 * @param name The name of the setting to modify.
964 * @param value The new value for the setting.
965 * @return true if the value was set, false on database errors
966 */
Jeff Sharkeybdfce2e2012-09-26 15:54:06 -0700967 private static boolean putSettingsGlobalBoolean(ContentResolver cr, String name, boolean value) {
968 return Settings.Global.putInt(cr, name, value ? 1 : 0);
Isaac Levyd7b3e6a2011-07-20 18:15:30 -0700969 }
970
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700971 /**
Yuhao Zhengb33227d2012-07-20 10:55:17 -0700972 * Bundle of good link count parameters
973 */
974 private static class GoodLinkTarget {
975 public final int RSSI_ADJ_DBM;
976 public final int SAMPLE_COUNT;
977 public final int REDUCE_TIME_MS;
978 public GoodLinkTarget(int adj, int count, int time) {
979 RSSI_ADJ_DBM = adj;
980 SAMPLE_COUNT = count;
981 REDUCE_TIME_MS = time;
982 }
983 }
984
985 /**
986 * Bundle of max avoidance time parameters
987 */
988 private static class MaxAvoidTime {
989 public final int TIME_MS;
990 public final int MIN_RSSI_DBM;
991 public MaxAvoidTime(int time, int rssi) {
992 TIME_MS = time;
993 MIN_RSSI_DBM = rssi;
994 }
995 }
996
997 /**
998 * Volume-weighted Exponential Moving Average (V-EMA)
999 * - volume-weighted: each update has its own weight (number of packets)
1000 * - exponential: O(1) time and O(1) space for both update and query
1001 * - moving average: reflect most recent results and expire old ones
1002 */
1003 private class VolumeWeightedEMA {
1004 private double mValue;
1005 private double mVolume;
1006 private double mProduct;
1007 private final double mAlpha;
1008
1009 public VolumeWeightedEMA(double coefficient) {
1010 mValue = 0.0;
1011 mVolume = 0.0;
1012 mProduct = 0.0;
1013 mAlpha = coefficient;
1014 }
1015
1016 public void update(double newValue, int newVolume) {
1017 if (newVolume <= 0) return;
1018 // core update formulas
1019 double newProduct = newValue * newVolume;
1020 mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct;
1021 mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume;
1022 mValue = mProduct / mVolume;
1023 }
1024 }
1025
1026 /**
1027 * Record (RSSI -> pakce loss %) mappings of one BSSID
1028 */
1029 private class BssidStatistics {
1030
1031 /* MAC address of this BSSID */
1032 private final String mBssid;
1033
1034 /* RSSI -> packet loss % mappings */
1035 private VolumeWeightedEMA[] mEntries;
1036 private int mRssiBase;
1037 private int mEntriesSize;
1038
1039 /* Target to send good link notification, set when poor link is detected */
1040 private int mGoodLinkTargetRssi;
1041 private int mGoodLinkTargetCount;
1042
1043 /* Index of GOOD_LINK_TARGET array */
1044 private int mGoodLinkTargetIndex;
1045
1046 /* Timestamps of some last events */
1047 private long mLastTimeSample;
1048 private long mLastTimeGood;
1049 private long mLastTimePoor;
1050
1051 /* Max time to avoid this BSSID */
1052 private long mBssidAvoidTimeMax;
1053
1054 /**
1055 * Constructor
1056 *
1057 * @param bssid is the address of this BSSID
1058 */
1059 public BssidStatistics(String bssid) {
1060 this.mBssid = bssid;
1061 mRssiBase = BSSID_STAT_RANGE_LOW_DBM;
1062 mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1;
1063 mEntries = new VolumeWeightedEMA[mEntriesSize];
1064 for (int i = 0; i < mEntriesSize; i++)
1065 mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD);
1066 }
1067
1068 /**
1069 * Update this BSSID cache
1070 *
1071 * @param rssi is the RSSI
1072 * @param value is the new instant loss value at this RSSI
1073 * @param volume is the volume for this single update
1074 */
1075 public void updateLoss(int rssi, double value, int volume) {
1076 if (volume <= 0) return;
1077 int index = rssi - mRssiBase;
1078 if (index < 0 || index >= mEntriesSize) return;
1079 mEntries[index].update(value, volume);
1080 if (DBG) {
1081 DecimalFormat df = new DecimalFormat("#.##");
1082 logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100)
1083 + "% volume=" + df.format(mEntries[index].mVolume));
1084 }
1085 }
1086
1087 /**
1088 * Get preset loss if the cache has insufficient data, observed from experiments.
1089 *
1090 * @param rssi is the input RSSI
1091 * @return preset loss of the given RSSI
1092 */
1093 public double presetLoss(int rssi) {
1094 if (rssi <= -90) return 1.0;
1095 if (rssi > 0) return 0.0;
1096
1097 if (sPresetLoss == null) {
1098 // pre-calculate all preset losses only once, then reuse them
1099 final int size = 90;
1100 sPresetLoss = new double[size];
1101 for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5);
1102 }
1103 return sPresetLoss[-rssi];
1104 }
1105
1106 /**
1107 * A poor link is detected, calculate a target RSSI to bring WiFi back.
1108 *
1109 * @param rssi is the current RSSI
1110 * @return true iff the current BSSID should be avoided
1111 */
1112 public boolean poorLinkDetected(int rssi) {
1113 if (DBG) logd("Poor link detected, rssi=" + rssi);
1114
1115 long now = SystemClock.elapsedRealtime();
1116 long lastGood = now - mLastTimeGood;
1117 long lastPoor = now - mLastTimePoor;
1118
1119 // reduce the difficulty of good link target if last avoidance was long time ago
1120 while (mGoodLinkTargetIndex > 0
1121 && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS)
1122 mGoodLinkTargetIndex--;
1123 mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT;
1124
1125 // scan for a target RSSI at which the link is good
1126 int from = rssi + GOOD_LINK_RSSI_RANGE_MIN;
1127 int to = rssi + GOOD_LINK_RSSI_RANGE_MAX;
1128 mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
1129 mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM;
1130 if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++;
1131
1132 // calculate max avoidance time to prevent avoiding forever
1133 int p = 0, pmax = MAX_AVOID_TIME.length - 1;
1134 while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++;
1135 long avoidMax = MAX_AVOID_TIME[p].TIME_MS;
1136
1137 // don't avoid if max avoidance time is 0 (RSSI is super high)
1138 if (avoidMax <= 0) return false;
1139
1140 // set max avoidance time, send poor link notification
1141 mBssidAvoidTimeMax = now + avoidMax;
1142
1143 if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount
1144 + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax);
1145
1146 return true;
1147 }
1148
1149 /**
1150 * A new BSSID is connected, recalculate target RSSI threshold
1151 */
1152 public void newLinkDetected() {
1153 // if this BSSID is currently being avoided, the reuse those values
1154 if (mBssidAvoidTimeMax > 0) {
1155 if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi
1156 + " count=" + mGoodLinkTargetCount);
1157 return;
1158 }
1159
1160 // calculate a new RSSI threshold for new link verifying
1161 int from = BSSID_STAT_RANGE_LOW_DBM;
1162 int to = BSSID_STAT_RANGE_HIGH_DBM;
1163 mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
1164 mGoodLinkTargetCount = 1;
1165 mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS;
1166 if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count="
1167 + mGoodLinkTargetCount);
1168 }
1169
1170 /**
1171 * Return the first RSSI within the range where loss[rssi] < threshold
1172 *
1173 * @param from start scanning from this RSSI
1174 * @param to stop scanning at this RSSI
1175 * @param threshold target threshold for scanning
1176 * @return target RSSI
1177 */
1178 public int findRssiTarget(int from, int to, double threshold) {
1179 from -= mRssiBase;
1180 to -= mRssiBase;
1181 int emptyCount = 0;
1182 int d = from < to ? 1 : -1;
1183 for (int i = from; i != to; i += d)
1184 // don't use a data point if it volume is too small (statistically unreliable)
1185 if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) {
1186 emptyCount = 0;
1187 if (mEntries[i].mValue < threshold) {
1188 // scan target found
1189 int rssi = mRssiBase + i;
1190 if (DBG) {
1191 DecimalFormat df = new DecimalFormat("#.##");
1192 logd("Scan target found: rssi=" + rssi + " threshold="
1193 + df.format(threshold * 100) + "% value="
1194 + df.format(mEntries[i].mValue * 100) + "% volume="
1195 + df.format(mEntries[i].mVolume));
1196 }
1197 return rssi;
1198 }
1199 } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) {
1200 // cache has insufficient data around this RSSI, use preset loss instead
1201 int rssi = mRssiBase + i;
1202 double lossPreset = presetLoss(rssi);
1203 if (lossPreset < threshold) {
1204 if (DBG) {
1205 DecimalFormat df = new DecimalFormat("#.##");
1206 logd("Scan target found: rssi=" + rssi + " threshold="
1207 + df.format(threshold * 100) + "% value="
1208 + df.format(lossPreset * 100) + "% volume=preset");
1209 }
1210 return rssi;
1211 }
1212 }
1213
1214 return mRssiBase + to;
1215 }
1216 }
Isaac Levy654f5092011-07-13 17:41:45 -07001217}