blob: fe97b93273055078a0f6cebc29102dd5c04b206f [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2008 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 com.android.server;
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.NetworkInfo;
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -080026import android.net.DhcpInfo;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070027import android.net.wifi.ScanResult;
28import android.net.wifi.WifiInfo;
29import android.net.wifi.WifiManager;
30import android.net.wifi.WifiStateTracker;
31import android.os.Handler;
32import android.os.Looper;
33import android.os.Message;
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -080034import android.provider.Settings;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070035import android.text.TextUtils;
36import android.util.Config;
37import android.util.Log;
38
39import java.io.IOException;
40import java.net.DatagramPacket;
41import java.net.DatagramSocket;
42import java.net.InetAddress;
43import java.net.SocketException;
44import java.net.SocketTimeoutException;
45import java.net.UnknownHostException;
46import java.util.List;
47import java.util.Random;
48
49/**
50 * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
51 * network with multiple access points. After the framework successfully
52 * connects to an access point, the watchdog verifies whether the DNS server is
53 * reachable. If not, the watchdog blacklists the current access point, leading
54 * to a connection on another access point within the same network.
55 * <p>
56 * The watchdog has a few safeguards:
57 * <ul>
58 * <li>Only monitor networks with multiple access points
59 * <li>Only check at most {@link #getMaxApChecks()} different access points
60 * within the network before giving up
61 * <p>
62 * The watchdog checks for connectivity on an access point by ICMP pinging the
63 * DNS. There are settings that allow disabling the watchdog, or tweaking the
64 * acceptable packet loss (and other various parameters).
65 * <p>
66 * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
67 * callbacks can come in on other threads, so we must queue messages to the main
68 * watchdog thread's handler. Most (if not all) state is only written to from
69 * the main thread.
70 *
71 * {@hide}
72 */
73public class WifiWatchdogService {
74 private static final String TAG = "WifiWatchdogService";
75 private static final boolean V = false || Config.LOGV;
76 private static final boolean D = true || Config.LOGD;
77
78 /*
79 * When this was "net.dns1", sometimes the mobile data's DNS was seen
80 * instead due to a race condition. All we really care about is the
81 * DHCP-replied DNS server anyway.
82 */
83 /** The system property whose value provides the current DNS address. */
84 private static final String SYSTEMPROPERTY_KEY_DNS = "dhcp.tiwlan0.dns1";
85
86 private Context mContext;
87 private ContentResolver mContentResolver;
88 private WifiStateTracker mWifiStateTracker;
89 private WifiManager mWifiManager;
90
91 /**
92 * The main watchdog thread.
93 */
94 private WifiWatchdogThread mThread;
95 /**
96 * The handler for the main watchdog thread.
97 */
98 private WifiWatchdogHandler mHandler;
99
100 /**
101 * The current watchdog state. Only written from the main thread!
102 */
103 private WatchdogState mState = WatchdogState.IDLE;
104 /**
105 * The SSID of the network that the watchdog is currently monitoring. Only
106 * touched in the main thread!
107 */
108 private String mSsid;
109 /**
110 * The number of access points in the current network ({@link #mSsid}) that
111 * have been checked. Only touched in the main thread!
112 */
113 private int mNumApsChecked;
114 /** Whether the current AP check should be canceled. */
115 private boolean mShouldCancel;
116
117 WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
118 mContext = context;
119 mContentResolver = context.getContentResolver();
120 mWifiStateTracker = wifiStateTracker;
121 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
122
123 createThread();
124
125 // The content observer to listen needs a handler, which createThread creates
126 registerForSettingsChanges();
127 if (isWatchdogEnabled()) {
128 registerForWifiBroadcasts();
129 }
130
131 if (V) {
132 myLogV("WifiWatchdogService: Created");
133 }
134 }
135
136 /**
137 * Observes the watchdog on/off setting, and takes action when changed.
138 */
139 private void registerForSettingsChanges() {
140 ContentResolver contentResolver = mContext.getContentResolver();
141 contentResolver.registerContentObserver(
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800142 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700143 new ContentObserver(mHandler) {
144 @Override
145 public void onChange(boolean selfChange) {
146 if (isWatchdogEnabled()) {
147 registerForWifiBroadcasts();
148 } else {
149 unregisterForWifiBroadcasts();
150 if (mHandler != null) {
151 mHandler.disableWatchdog();
152 }
153 }
154 }
155 });
156 }
157
158 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800159 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700160 */
161 private boolean isWatchdogEnabled() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800162 return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700163 }
164
165 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800166 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700167 */
168 private int getApCount() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800169 return Settings.Secure.getInt(mContentResolver,
170 Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700171 }
172
173 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800174 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700175 */
176 private int getInitialIgnoredPingCount() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800177 return Settings.Secure.getInt(mContentResolver,
178 Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700179 }
180
181 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800182 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700183 */
184 private int getPingCount() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800185 return Settings.Secure.getInt(mContentResolver,
186 Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700187 }
188
189 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800190 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700191 */
192 private int getPingTimeoutMs() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800193 return Settings.Secure.getInt(mContentResolver,
194 Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700195 }
196
197 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800198 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700199 */
200 private int getPingDelayMs() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800201 return Settings.Secure.getInt(mContentResolver,
202 Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700203 }
204
205 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800206 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700207 */
208 private int getAcceptablePacketLossPercentage() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800209 return Settings.Secure.getInt(mContentResolver,
210 Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700211 }
212
213 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800214 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700215 */
216 private int getMaxApChecks() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800217 return Settings.Secure.getInt(mContentResolver,
218 Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700219 }
220
221 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800222 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700223 */
224 private boolean isBackgroundCheckEnabled() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800225 return Settings.Secure.getInt(mContentResolver,
226 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700227 }
228
229 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800230 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700231 */
232 private int getBackgroundCheckDelayMs() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800233 return Settings.Secure.getInt(mContentResolver,
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800234 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700235 }
236
237 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800238 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700239 */
240 private int getBackgroundCheckTimeoutMs() {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800241 return Settings.Secure.getInt(mContentResolver,
242 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700243 }
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800244
245 /**
246 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
247 * @return the comma-separated list of SSIDs
248 */
249 private String getWatchList() {
250 return Settings.Secure.getString(mContentResolver,
251 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
252 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700253
254 /**
255 * Registers to receive the necessary Wi-Fi broadcasts.
256 */
257 private void registerForWifiBroadcasts() {
258 IntentFilter intentFilter = new IntentFilter();
259 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
260 intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
261 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
262 mContext.registerReceiver(mReceiver, intentFilter);
263 }
264
265 /**
266 * Unregisters from receiving the Wi-Fi broadcasts.
267 */
268 private void unregisterForWifiBroadcasts() {
269 mContext.unregisterReceiver(mReceiver);
270 }
271
272 /**
273 * Creates the main watchdog thread, including waiting for the handler to be
274 * created.
275 */
276 private void createThread() {
277 mThread = new WifiWatchdogThread();
278 mThread.start();
279 waitForHandlerCreation();
280 }
281
282 /**
283 * Waits for the main watchdog thread to create the handler.
284 */
285 private void waitForHandlerCreation() {
286 synchronized(this) {
287 while (mHandler == null) {
288 try {
289 // Wait for the handler to be set by the other thread
290 wait();
291 } catch (InterruptedException e) {
292 Log.e(TAG, "Interrupted while waiting on handler.");
293 }
294 }
295 }
296 }
297
298 // Utility methods
299
300 /**
301 * Logs with the current thread.
302 */
303 private static void myLogV(String message) {
304 Log.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
305 }
306
307 private static void myLogD(String message) {
308 Log.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
309 }
310
311 /**
312 * Gets the DNS of the current AP.
313 *
314 * @return The DNS of the current AP.
315 */
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800316 private int getDns() {
317 DhcpInfo addressInfo = mWifiManager.getDhcpInfo();
318 if (addressInfo != null) {
319 return addressInfo.dns1;
320 } else {
321 return -1;
322 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700323 }
324
325 /**
326 * Checks whether the DNS can be reached using multiple attempts according
327 * to the current setting values.
328 *
329 * @return Whether the DNS is reachable
330 */
331 private boolean checkDnsConnectivity() {
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800332 int dns = getDns();
333 if (dns == -1) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700334 if (V) {
335 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
336 }
337 return false;
338 }
339
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800340 if (V) {
341 myLogV("checkDnsConnectivity: Checking 0x" +
342 Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity");
343 }
344
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700345 int numInitialIgnoredPings = getInitialIgnoredPingCount();
346 int numPings = getPingCount();
347 int pingDelay = getPingDelayMs();
348 int acceptableLoss = getAcceptablePacketLossPercentage();
349
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800350 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700351 int ignoredPingCounter = 0;
352 int pingCounter = 0;
353 int successCounter = 0;
354
355 // No connectivity check needed
356 if (numPings == 0) {
357 return true;
358 }
359
360 // Do the initial pings that we ignore
361 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
362 if (shouldCancel()) return false;
363
364 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
365 if (dnsAlive) {
366 /*
367 * Successful "ignored" pings are *not* ignored (they count in the total number
368 * of pings), but failures are really ignored.
369 */
370 pingCounter++;
371 successCounter++;
372 }
373
374 if (V) {
375 Log.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
376 }
377
378 if (shouldCancel()) return false;
379
380 try {
381 Thread.sleep(pingDelay);
382 } catch (InterruptedException e) {
383 Log.w(TAG, "Interrupted while pausing between pings", e);
384 }
385 }
386
387 // Do the pings that we use to measure packet loss
388 for (; pingCounter < numPings; pingCounter++) {
389 if (shouldCancel()) return false;
390
391 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
392 successCounter++;
393 if (V) {
394 Log.v(TAG, " +");
395 }
396 } else {
397 if (V) {
398 Log.v(TAG, " -");
399 }
400 }
401
402 if (shouldCancel()) return false;
403
404 try {
405 Thread.sleep(pingDelay);
406 } catch (InterruptedException e) {
407 Log.w(TAG, "Interrupted while pausing between pings", e);
408 }
409 }
410
411 int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
412 if (D) {
413 Log.d(TAG, packetLossPercentage
414 + "% packet loss (acceptable is " + acceptableLoss + "%)");
415 }
416
417 return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
418 }
419
420 private boolean backgroundCheckDnsConnectivity() {
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800421 int dns = getDns();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700422 if (false && V) {
423 myLogV("backgroundCheckDnsConnectivity: Background checking " + dns +
424 " for connectivity");
425 }
426
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800427 if (dns == -1) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700428 if (V) {
429 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
430 }
431 return false;
432 }
433
434 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
435 }
436
437 /**
438 * Signals the current action to cancel.
439 */
440 private void cancelCurrentAction() {
441 mShouldCancel = true;
442 }
443
444 /**
445 * Helper to check whether to cancel.
446 *
447 * @return Whether to cancel processing the action.
448 */
449 private boolean shouldCancel() {
450 if (V && mShouldCancel) {
451 myLogV("shouldCancel: Cancelling");
452 }
453
454 return mShouldCancel;
455 }
456
457 // Wi-Fi initiated callbacks (could be executed in another thread)
458
459 /**
460 * Called when connected to an AP (this can be the next AP in line, or
461 * it can be a completely different network).
462 *
463 * @param ssid The SSID of the access point.
464 * @param bssid The BSSID of the access point.
465 */
466 private void onConnected(String ssid, String bssid) {
467 if (V) {
468 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
469 }
470
471 /*
472 * The current action being processed by the main watchdog thread is now
473 * stale, so cancel it.
474 */
475 cancelCurrentAction();
476
477 if ((mSsid == null) || !mSsid.equals(ssid)) {
478 /*
479 * This is a different network than what the main watchdog thread is
480 * processing, dispatch the network change message on the main thread.
481 */
482 mHandler.dispatchNetworkChanged(ssid);
483 }
484
485 if (requiresWatchdog(ssid, bssid)) {
486 if (D) {
487 myLogD(ssid + " (" + bssid + ") requires the watchdog");
488 }
489
490 // This access point requires a watchdog, so queue the check on the main thread
491 mHandler.checkAp(new AccessPoint(ssid, bssid));
492
493 } else {
494 if (D) {
495 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
496 }
497
498 // This access point does not require a watchdog, so queue idle on the main thread
499 mHandler.idle();
500 }
501 }
502
503 /**
504 * Called when Wi-Fi is enabled.
505 */
506 private void onEnabled() {
507 cancelCurrentAction();
508 // Queue a hard-reset of the state on the main thread
509 mHandler.reset();
510 }
511
512 /**
513 * Called when disconnected (or some other event similar to being disconnected).
514 */
515 private void onDisconnected() {
516 if (V) {
517 myLogV("onDisconnected");
518 }
519
520 /*
521 * Disconnected from an access point, the action being processed by the
522 * watchdog thread is now stale, so cancel it.
523 */
524 cancelCurrentAction();
525 // Dispatch the disconnected to the main watchdog thread
526 mHandler.dispatchDisconnected();
527 // Queue the action to go idle
528 mHandler.idle();
529 }
530
531 /**
532 * Checks whether an access point requires watchdog monitoring.
533 *
534 * @param ssid The SSID of the access point.
535 * @param bssid The BSSID of the access point.
536 * @return Whether the access point/network should be monitored by the
537 * watchdog.
538 */
539 private boolean requiresWatchdog(String ssid, String bssid) {
540 if (V) {
541 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
542 }
543
544 WifiInfo info = null;
545 if (ssid == null) {
546 /*
547 * This is called from a Wi-Fi callback, so assume the WifiInfo does
548 * not have stale data.
549 */
550 info = mWifiManager.getConnectionInfo();
551 ssid = info.getSSID();
552 if (ssid == null) {
553 // It's still null, give up
554 if (V) {
555 Log.v(TAG, " Invalid SSID, returning false");
556 }
557 return false;
558 }
559 }
560
561 if (TextUtils.isEmpty(bssid)) {
562 // Similar as above
563 if (info == null) {
564 info = mWifiManager.getConnectionInfo();
565 }
566 bssid = info.getBSSID();
567 if (TextUtils.isEmpty(bssid)) {
568 // It's still null, give up
569 if (V) {
570 Log.v(TAG, " Invalid BSSID, returning false");
571 }
572 return false;
573 }
574 }
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800575
576 if (!isOnWatchList(ssid)) {
577 if (V) {
578 Log.v(TAG, " SSID not on watch list, returning false");
579 }
580 return false;
581 }
582
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700583 // The watchdog only monitors networks with multiple APs
584 if (!hasRequiredNumberOfAps(ssid)) {
585 return false;
586 }
587
588 return true;
589 }
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -0800590
591 private boolean isOnWatchList(String ssid) {
592 String watchList;
593
594 if (ssid == null || (watchList = getWatchList()) == null) {
595 return false;
596 }
597
598 String[] list = watchList.split(" *, *");
599
600 for (String name : list) {
601 if (ssid.equals(name)) {
602 return true;
603 }
604 }
605
606 return false;
607 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700608
609 /**
610 * Checks if the current scan results have multiple access points with an SSID.
611 *
612 * @param ssid The SSID to check.
613 * @return Whether the SSID has multiple access points.
614 */
615 private boolean hasRequiredNumberOfAps(String ssid) {
616 List<ScanResult> results = mWifiManager.getScanResults();
617 if (results == null) {
618 if (V) {
619 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
620 }
621 return false;
622 }
623
624 int numApsRequired = getApCount();
625 int numApsFound = 0;
626 int resultsSize = results.size();
627 for (int i = 0; i < resultsSize; i++) {
628 ScanResult result = results.get(i);
629 if (result == null) continue;
630 if (result.SSID == null) continue;
631
632 if (result.SSID.equals(ssid)) {
633 numApsFound++;
634
635 if (numApsFound >= numApsRequired) {
636 if (V) {
637 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
638 }
639 return true;
640 }
641 }
642 }
643
644 if (V) {
645 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
646 }
647 return false;
648 }
649
650 // Watchdog logic (assume all of these methods will be in our main thread)
651
652 /**
653 * Handles a Wi-Fi network change (for example, from networkA to networkB).
654 */
655 private void handleNetworkChanged(String ssid) {
656 // Set the SSID being monitored to the new SSID
657 mSsid = ssid;
658 // Set various state to that when being idle
659 setIdleState(true);
660 }
661
662 /**
663 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
664 *
665 * @param ap The access point to check.
666 */
667 private void handleCheckAp(AccessPoint ap) {
668 // Reset the cancel state since this is the entry point of this action
669 mShouldCancel = false;
670
671 if (V) {
672 myLogV("handleCheckAp: AccessPoint: " + ap);
673 }
674
675 // Make sure we are not sleeping
676 if (mState == WatchdogState.SLEEP) {
677 if (V) {
678 Log.v(TAG, " Sleeping (in " + mSsid + "), so returning");
679 }
680 return;
681 }
682
683 mState = WatchdogState.CHECKING_AP;
684
685 /*
686 * Checks to make sure we haven't exceeded the max number of checks
687 * we're allowed per network
688 */
689 mNumApsChecked++;
690 if (mNumApsChecked > getMaxApChecks()) {
691 if (V) {
692 Log.v(TAG, " Passed the max attempts (" + getMaxApChecks()
693 + "), going to sleep for " + mSsid);
694 }
695 mHandler.sleep(mSsid);
696 return;
697 }
698
699 // Do the check
700 boolean isApAlive = checkDnsConnectivity();
701
702 if (V) {
703 Log.v(TAG, " Is it alive: " + isApAlive);
704 }
705
706 // Take action based on results
707 if (isApAlive) {
708 handleApAlive(ap);
709 } else {
710 handleApUnresponsive(ap);
711 }
712 }
713
714 /**
715 * Handles the case when an access point is alive.
716 *
717 * @param ap The access point.
718 */
719 private void handleApAlive(AccessPoint ap) {
720 // Check whether we are stale and should cancel
721 if (shouldCancel()) return;
722 // We're satisfied with this AP, so go idle
723 setIdleState(false);
724
725 if (D) {
726 myLogD("AP is alive: " + ap.toString());
727 }
728
729 // Queue the next action to be a background check
730 mHandler.backgroundCheckAp(ap);
731 }
732
733 /**
734 * Handles an unresponsive AP by blacklisting it.
735 *
736 * @param ap The access point.
737 */
738 private void handleApUnresponsive(AccessPoint ap) {
739 // Check whether we are stale and should cancel
740 if (shouldCancel()) return;
741 // This AP is "bad", switch to another
742 mState = WatchdogState.SWITCHING_AP;
743
744 if (D) {
745 myLogD("AP is dead: " + ap.toString());
746 }
747
748 // Black list this "bad" AP, this will cause an attempt to connect to another
749 blacklistAp(ap.bssid);
750 }
751
752 private void blacklistAp(String bssid) {
753 if (TextUtils.isEmpty(bssid)) {
754 return;
755 }
756
757 // Before taking action, make sure we should not cancel our processing
758 if (shouldCancel()) return;
759
760 if (!mWifiStateTracker.addToBlacklist(bssid)) {
761 // There's a known bug where this method returns failure on success
762 //Log.e(TAG, "Blacklisting " + bssid + " failed");
763 }
764
765 if (D) {
766 myLogD("Blacklisting " + bssid);
767 }
768 }
769
770 /**
771 * Handles a single background check. If it fails, it should trigger a
772 * normal check. If it succeeds, it should queue another background check.
773 *
774 * @param ap The access point to do a background check for. If this is no
775 * longer the current AP, it is okay to return without any
776 * processing.
777 */
778 private void handleBackgroundCheckAp(AccessPoint ap) {
779 // Reset the cancel state since this is the entry point of this action
780 mShouldCancel = false;
781
782 if (false && V) {
783 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
784 }
785
786 // Make sure we are not sleeping
787 if (mState == WatchdogState.SLEEP) {
788 if (V) {
789 Log.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
790 }
791 return;
792 }
793
794 // Make sure the AP we're supposed to be background checking is still the active one
795 WifiInfo info = mWifiManager.getConnectionInfo();
796 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
797 if (V) {
798 myLogV("handleBackgroundCheckAp: We are no longer connected to "
799 + ap + ", and instead are on " + info);
800 }
801 return;
802 }
803
804 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
805 if (V) {
806 myLogV("handleBackgroundCheckAp: We are no longer connected to "
807 + ap + ", and instead are on " + info);
808 }
809 return;
810 }
811
812 // Do the check
813 boolean isApAlive = backgroundCheckDnsConnectivity();
814
815 if (V && !isApAlive) {
816 Log.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
817 }
818
819 if (shouldCancel()) {
820 return;
821 }
822
823 // Take action based on results
824 if (isApAlive) {
825 // Queue another background check
826 mHandler.backgroundCheckAp(ap);
827
828 } else {
829 if (D) {
830 myLogD("Background check failed for " + ap.toString());
831 }
832
833 // Queue a normal check, so it can take proper action
834 mHandler.checkAp(ap);
835 }
836 }
837
838 /**
839 * Handles going to sleep for this network. Going to sleep means we will not
840 * monitor this network anymore.
841 *
842 * @param ssid The network that will not be monitored anymore.
843 */
844 private void handleSleep(String ssid) {
845 // Make sure the network we're trying to sleep in is still the current network
846 if (ssid != null && ssid.equals(mSsid)) {
847 mState = WatchdogState.SLEEP;
848
849 if (D) {
850 myLogD("Going to sleep for " + ssid);
851 }
852
853 /*
854 * Before deciding to go to sleep, we may have checked a few APs
855 * (and blacklisted them). Clear the blacklist so the AP with best
856 * signal is chosen.
857 */
858 if (!mWifiStateTracker.clearBlacklist()) {
859 // There's a known bug where this method returns failure on success
860 //Log.e(TAG, "Clearing blacklist failed");
861 }
862
863 if (V) {
864 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
865 }
866 }
867 }
868
869 /**
870 * Handles an access point disconnection.
871 */
872 private void handleDisconnected() {
873 /*
874 * We purposefully do not change mSsid to null. This is to handle
875 * disconnected followed by connected better (even if there is some
876 * duration in between). For example, if the watchdog went to sleep in a
877 * network, and then the phone goes to sleep, when the phone wakes up we
878 * still want to be in the sleeping state. When the phone went to sleep,
879 * we would have gotten a disconnected event which would then set mSsid
880 * = null. This is bad, since the following connect would cause us to do
881 * the "network is good?" check all over again. */
882
883 /*
884 * Set the state as if we were idle (don't come out of sleep, only
885 * hard reset and network changed should do that.
886 */
887 setIdleState(false);
888 }
889
890 /**
891 * Handles going idle. Idle means we are satisfied with the current state of
892 * things, but if a new connection occurs we'll re-evaluate.
893 */
894 private void handleIdle() {
895 // Reset the cancel state since this is the entry point for this action
896 mShouldCancel = false;
897
898 if (V) {
899 myLogV("handleSwitchToIdle");
900 }
901
902 // If we're sleeping, don't do anything
903 if (mState == WatchdogState.SLEEP) {
904 Log.v(TAG, " Sleeping (in " + mSsid + "), so returning");
905 return;
906 }
907
908 // Set the idle state
909 setIdleState(false);
910
911 if (V) {
912 Log.v(TAG, " Set state to IDLE");
913 }
914 }
915
916 /**
917 * Sets the state as if we are going idle.
918 */
919 private void setIdleState(boolean forceIdleState) {
920 // Setting idle state does not kick us out of sleep unless the forceIdleState is set
921 if (forceIdleState || (mState != WatchdogState.SLEEP)) {
922 mState = WatchdogState.IDLE;
923 }
924 mNumApsChecked = 0;
925 }
926
927 /**
928 * Handles a hard reset. A hard reset is rarely used, but when used it
929 * should revert anything done by the watchdog monitoring.
930 */
931 private void handleReset() {
932 mWifiStateTracker.clearBlacklist();
933 setIdleState(true);
934 }
935
936 // Inner classes
937
938 /**
939 * Possible states for the watchdog to be in.
940 */
941 private static enum WatchdogState {
942 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
943 IDLE,
944 /** The watchdog is sleeping, so it will not try any AP checks for the network. */
945 SLEEP,
946 /** The watchdog is currently checking an AP for connectivity. */
947 CHECKING_AP,
948 /** The watchdog is switching to another AP in the network. */
949 SWITCHING_AP
950 }
951
952 /**
953 * The main thread for the watchdog monitoring. This will be turned into a
954 * {@link Looper} thread.
955 */
956 private class WifiWatchdogThread extends Thread {
957 WifiWatchdogThread() {
958 super("WifiWatchdogThread");
959 }
960
961 @Override
962 public void run() {
963 // Set this thread up so the handler will work on it
964 Looper.prepare();
965
966 synchronized(WifiWatchdogService.this) {
967 mHandler = new WifiWatchdogHandler();
968
969 // Notify that the handler has been created
970 WifiWatchdogService.this.notify();
971 }
972
973 // Listen for messages to the handler
974 Looper.loop();
975 }
976 }
977
978 /**
979 * The main thread's handler. There are 'actions', and just general
980 * 'messages'. There should only ever be one 'action' in the queue (aside
981 * from the one being processed, if any). There may be multiple messages in
982 * the queue. So, actions are replaced by more recent actions, where as
983 * messages will be executed for sure. Messages end up being used to just
984 * change some state, and not really take any action.
985 * <p>
986 * There is little logic inside this class, instead methods of the form
987 * "handle___" are called in the main {@link WifiWatchdogService}.
988 */
989 private class WifiWatchdogHandler extends Handler {
990 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
991 static final int ACTION_CHECK_AP = 1;
992 /** Go into the idle state. */
993 static final int ACTION_IDLE = 2;
994 /**
995 * Performs a periodic background check whether the AP is still "good".
996 * The object will be an {@link AccessPoint}.
997 */
998 static final int ACTION_BACKGROUND_CHECK_AP = 3;
999
1000 /**
1001 * Go to sleep for the current network. We are conservative with making
1002 * this a message rather than action. We want to make sure our main
1003 * thread sees this message, but if it were an action it could be
1004 * removed from the queue and replaced by another action. The main
1005 * thread will ensure when it sees the message that the state is still
1006 * valid for going to sleep.
1007 * <p>
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001008 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001009 */
1010 static final int MESSAGE_SLEEP = 101;
1011 /** Disables the watchdog. */
1012 static final int MESSAGE_DISABLE_WATCHDOG = 102;
1013 /** The network has changed. */
1014 static final int MESSAGE_NETWORK_CHANGED = 103;
1015 /** The current access point has disconnected. */
1016 static final int MESSAGE_DISCONNECTED = 104;
1017 /** Performs a hard-reset on the watchdog state. */
1018 static final int MESSAGE_RESET = 105;
1019
1020 void checkAp(AccessPoint ap) {
1021 removeAllActions();
1022 sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
1023 }
1024
1025 void backgroundCheckAp(AccessPoint ap) {
1026 if (!isBackgroundCheckEnabled()) return;
1027
1028 removeAllActions();
1029 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
1030 getBackgroundCheckDelayMs());
1031 }
1032
1033 void idle() {
1034 removeAllActions();
1035 sendMessage(obtainMessage(ACTION_IDLE));
1036 }
1037
1038 void sleep(String ssid) {
1039 removeAllActions();
1040 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
1041 }
1042
1043 void disableWatchdog() {
1044 removeAllActions();
1045 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
1046 }
1047
1048 void dispatchNetworkChanged(String ssid) {
1049 removeAllActions();
1050 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
1051 }
1052
1053 void dispatchDisconnected() {
1054 removeAllActions();
1055 sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
1056 }
1057
1058 void reset() {
1059 removeAllActions();
1060 sendMessage(obtainMessage(MESSAGE_RESET));
1061 }
1062
1063 private void removeAllActions() {
1064 removeMessages(ACTION_CHECK_AP);
1065 removeMessages(ACTION_IDLE);
1066 removeMessages(ACTION_BACKGROUND_CHECK_AP);
1067 }
1068
1069 @Override
1070 public void handleMessage(Message msg) {
1071 switch (msg.what) {
1072 case MESSAGE_NETWORK_CHANGED:
1073 handleNetworkChanged((String) msg.obj);
1074 break;
1075 case ACTION_CHECK_AP:
1076 handleCheckAp((AccessPoint) msg.obj);
1077 break;
1078 case ACTION_BACKGROUND_CHECK_AP:
1079 handleBackgroundCheckAp((AccessPoint) msg.obj);
1080 break;
1081 case MESSAGE_SLEEP:
1082 handleSleep((String) msg.obj);
1083 break;
1084 case ACTION_IDLE:
1085 handleIdle();
1086 break;
1087 case MESSAGE_DISABLE_WATCHDOG:
1088 handleIdle();
1089 break;
1090 case MESSAGE_DISCONNECTED:
1091 handleDisconnected();
1092 break;
1093 case MESSAGE_RESET:
1094 handleReset();
1095 break;
1096 }
1097 }
1098 }
1099
1100 /**
1101 * Receives Wi-Fi broadcasts.
1102 * <p>
1103 * There is little logic in this class, instead methods of the form "on___"
1104 * are called in the {@link WifiWatchdogService}.
1105 */
1106 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1107
1108 @Override
1109 public void onReceive(Context context, Intent intent) {
1110 final String action = intent.getAction();
1111 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
1112 handleNetworkStateChanged(
1113 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
1114 } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
1115 handleSupplicantConnectionChanged(
1116 intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false));
1117 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
1118 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
1119 WifiManager.WIFI_STATE_UNKNOWN));
1120 }
1121 }
1122
1123 private void handleNetworkStateChanged(NetworkInfo info) {
1124 if (V) {
1125 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
1126 + info);
1127 }
1128
1129 switch (info.getState()) {
1130 case CONNECTED:
1131 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
1132 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
1133 if (V) {
1134 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
1135 + wifiInfo.getSSID()
1136 + ", BSSID: "
1137 + wifiInfo.getBSSID() + ", ignoring event");
1138 }
1139 return;
1140 }
1141 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
1142 break;
1143
1144 case DISCONNECTED:
1145 onDisconnected();
1146 break;
1147 }
1148 }
1149
1150 private void handleSupplicantConnectionChanged(boolean connected) {
1151 if (!connected) {
1152 onDisconnected();
1153 }
1154 }
1155
1156 private void handleWifiStateChanged(int wifiState) {
1157 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
1158 onDisconnected();
1159 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
1160 onEnabled();
1161 }
1162 }
1163 };
1164
1165 /**
1166 * Describes an access point by its SSID and BSSID.
1167 */
1168 private static class AccessPoint {
1169 String ssid;
1170 String bssid;
1171
1172 AccessPoint(String ssid, String bssid) {
1173 this.ssid = ssid;
1174 this.bssid = bssid;
1175 }
1176
1177 private boolean hasNull() {
1178 return ssid == null || bssid == null;
1179 }
1180
1181 @Override
1182 public boolean equals(Object o) {
1183 if (!(o instanceof AccessPoint)) return false;
1184 AccessPoint otherAp = (AccessPoint) o;
1185 boolean iHaveNull = hasNull();
1186 // Either we both have a null, or our SSIDs and BSSIDs are equal
1187 return (iHaveNull && otherAp.hasNull()) ||
1188 (otherAp.bssid != null && ssid.equals(otherAp.ssid)
1189 && bssid.equals(otherAp.bssid));
1190 }
1191
1192 @Override
1193 public int hashCode() {
1194 if (ssid == null || bssid == null) return 0;
1195 return ssid.hashCode() + bssid.hashCode();
1196 }
1197
1198 @Override
1199 public String toString() {
1200 return ssid + " (" + bssid + ")";
1201 }
1202 }
1203
1204 /**
1205 * Performs a simple DNS "ping" by sending a "server status" query packet to
1206 * the DNS server. As long as the server replies, we consider it a success.
1207 * <p>
1208 * We do not use a simple hostname lookup because that could be cached and
1209 * the API may not differentiate between a time out and a failure lookup
1210 * (which we really care about).
1211 */
1212 private static class DnsPinger {
1213
1214 /** Number of bytes for the query */
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001215 private static final int DNS_QUERY_BASE_SIZE = 33;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001216
1217 /** The DNS port */
1218 private static final int DNS_PORT = 53;
1219
1220 /** Used to generate IDs */
1221 private static Random sRandom = new Random();
1222
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -08001223 static boolean isDnsReachable(int dns, int timeout) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001224 try {
1225 DatagramSocket socket = new DatagramSocket();
1226
1227 // Set some socket properties
1228 socket.setSoTimeout(timeout);
1229
1230 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
1231 fillQuery(buf);
1232
1233 // Send the DNS query
The Android Open Source Project22f7dfd2009-01-20 14:03:58 -08001234 byte parts[] = new byte[4];
1235 parts[0] = (byte)(dns & 0xff);
1236 parts[1] = (byte)((dns >> 8) & 0xff);
1237 parts[2] = (byte)((dns >> 16) & 0xff);
1238 parts[3] = (byte)((dns >> 24) & 0xff);
1239
1240 InetAddress dnsAddress = InetAddress.getByAddress(parts);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001241 DatagramPacket packet = new DatagramPacket(buf,
1242 buf.length, dnsAddress, DNS_PORT);
1243 socket.send(packet);
1244
1245 // Wait for reply (blocks for the above timeout)
1246 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
1247 socket.receive(replyPacket);
1248
1249 // If a timeout occurred, an exception would have been thrown. We got a reply!
1250 return true;
1251
1252 } catch (SocketException e) {
1253 if (V) {
1254 Log.v(TAG, "DnsPinger.isReachable received SocketException", e);
1255 }
1256 return false;
1257
1258 } catch (UnknownHostException e) {
1259 if (V) {
1260 Log.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
1261 }
1262 return false;
1263
1264 } catch (SocketTimeoutException e) {
1265 return false;
1266
1267 } catch (IOException e) {
1268 if (V) {
1269 Log.v(TAG, "DnsPinger.isReachable got an IOException", e);
1270 }
1271 return false;
1272
1273 } catch (Exception e) {
1274 if (V || Config.LOGD) {
1275 Log.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
1276 }
1277 return false;
1278 }
1279 }
1280
1281 private static void fillQuery(byte[] buf) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001282
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001283 /*
1284 * See RFC2929 (though the bit tables in there are misleading for
1285 * us. For example, the recursion desired bit is the 0th bit for us,
1286 * but looking there it would appear as the 7th bit of the byte
1287 */
1288
1289 // Make sure it's all zeroed out
1290 for (int i = 0; i < buf.length; i++) buf[i] = 0;
1291
1292 // Form a query for www.android.com
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001293
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001294 // [0-1] bytes are an ID, generate random ID for this query
1295 buf[0] = (byte) sRandom.nextInt(256);
1296 buf[1] = (byte) sRandom.nextInt(256);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001297
1298 // [2-3] bytes are for flags.
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001299 buf[2] = 1; // Recursion desired
1300
1301 // [4-5] bytes are for the query count
1302 buf[5] = 1; // One query
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001303
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001304 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
1305
1306 // [12-15] for www
1307 writeString(buf, 12, "www");
1308
1309 // [16-23] for android
1310 writeString(buf, 16, "android");
1311
1312 // [24-27] for com
1313 writeString(buf, 24, "com");
1314
1315 // [29-30] bytes are for QTYPE, set to 1
1316 buf[30] = 1;
1317
1318 // [31-32] bytes are for QCLASS, set to 1
1319 buf[32] = 1;
1320 }
1321
1322 private static void writeString(byte[] buf, int startPos, String string) {
1323 int pos = startPos;
1324
1325 // Write the length first
1326 buf[pos++] = (byte) string.length();
1327 for (int i = 0; i < string.length(); i++) {
1328 buf[pos++] = (byte) string.charAt(i);
1329 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001330 }
1331 }
1332}