diff options
| -rw-r--r-- | core/java/com/android/internal/util/Protocol.java | 1 | ||||
| -rw-r--r-- | data/fonts/fallback_fonts.xml | 47 | ||||
| -rw-r--r-- | data/fonts/fonts.mk | 4 | ||||
| -rw-r--r-- | data/fonts/system_fonts.xml | 68 | ||||
| -rw-r--r-- | data/fonts/vendor_fonts.xml | 51 | ||||
| -rw-r--r-- | media/libstagefright/XINGSeeker.cpp | 6 | ||||
| -rw-r--r-- | media/libstagefright/httplive/LiveSession.cpp | 123 | ||||
| -rw-r--r-- | media/libstagefright/include/LiveSession.h | 16 | ||||
| -rw-r--r-- | media/libstagefright/include/XINGSeeker.h | 2 | ||||
| -rw-r--r-- | services/java/com/android/server/BackupManagerService.java | 91 | ||||
| -rw-r--r-- | services/java/com/android/server/WifiService.java | 13 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.cpp | 12 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiWatchdogService.java | 765 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiWatchdogStateMachine.java | 825 |
14 files changed, 1227 insertions, 797 deletions
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index b754d94597d0..69b80d9a91e5 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -40,6 +40,7 @@ public class Protocol { /** Non system protocols */ public static final int BASE_WIFI = 0x00020000; + public static final int BASE_WIFI_WATCHDOG = 0x00021000; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml new file mode 100644 index 000000000000..c0d91533c5d3 --- /dev/null +++ b/data/fonts/fallback_fonts.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Fallback Fonts + + This file specifies the fonts, and the priority order, that will be searched for any + glyphs not handled by the default fonts specified in /system/etc/system_fonts.xml. + Each entry consists of a family tag and a list of files (file names) which support that + family. The fonts for each family are listed in the order of the styles that they + handle (the order is: regular, bold, italic, and bold-italic). The order in which the + families are listed in this file represents the order in which these fallback fonts + will be searched for glyphs that are not supported by the default system fonts (which are + found in /system/etc/system_fonts.xml). + + Note that there is not nameset for fallback fonts, unlike the fonts specified in + system_fonts.xml. The ability to support specific names in fallback fonts may be supported + in the future. For now, the lack of files entries here is an indicator to the system that + these are fallback fonts, instead of default named system fonts. + + There is another optional file in /vendor/etc/fallback_fonts.xml. That file can be used to + provide references to other font families that should be used in addition to the default + fallback fonts. That file can also specify the order in which the fallback fonts should be + searched, to ensure that a vendor-provided font will be used before another fallback font + which happens to handle the same glyph. +--> +<familyset> + <family> + <fileset> + <file>DroidSansArabic.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>DroidSansHebrew-Regular.ttf</file> + <file>DroidSansHebrew-Bold.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>DroidSansThai.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>DroidSansFallback.ttf</file> + </fileset> + </family> +</familyset> diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk index d222c0b4d20e..57a1bab04c6d 100644 --- a/data/fonts/fonts.mk +++ b/data/fonts/fonts.mk @@ -30,4 +30,6 @@ PRODUCT_COPY_FILES := \ frameworks/base/data/fonts/DroidSansFallback.ttf:system/fonts/DroidSansFallback.ttf \ frameworks/base/data/fonts/AndroidClock.ttf:system/fonts/AndroidClock.ttf \ frameworks/base/data/fonts/AndroidClock_Highlight.ttf:system/fonts/AndroidClock_Highlight.ttf \ - frameworks/base/data/fonts/AndroidClock_Solid.ttf:system/fonts/AndroidClock_Solid.ttf + frameworks/base/data/fonts/AndroidClock_Solid.ttf:system/fonts/AndroidClock_Solid.ttf \ + frameworks/base/data/fonts/system_fonts.xml:system/etc/system_fonts.xml \ + frameworks/base/data/fonts/fallback_fonts.xml:system/etc/fallback_fonts.xml diff --git a/data/fonts/system_fonts.xml b/data/fonts/system_fonts.xml new file mode 100644 index 000000000000..8d8d02061b16 --- /dev/null +++ b/data/fonts/system_fonts.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + System Fonts + + This file lists the font families that will be used by default for all supported glyphs. + Each entry consists of a family, various names that are supported by that family, and + up to four font files. The font files are listed in the order of the styles which they + support: regular, bold, italic and bold-italic. If less than four styles are listed, then + the styles with no associated font file will be supported by the other font files listed. + + The first family is also the default font, which handles font request that have not specified + specific font names. + + Any glyph that is not handled by the system fonts will cause a search of the fallback fonts. + The default fallback fonts are specified in the file /system/etc/fallback_fonts.xml, and there + is an optional file which may be supplied by vendors to specify other fallback fonts to use + in /vendor/etc/fallback_fonts.xml. +--> +<familyset> + + <family> + <nameset> + <name>sans-serif</name> + <name>arial</name> + <name>helvetica</name> + <name>tahoma</name> + <name>verdana</name> + </nameset> + <fileset> + <file>DroidSans.ttf</file> + <file>DroidSans-Bold.ttf</file> + </fileset> + </family> + + <family> + <nameset> + <name>serif</name> + <name>times</name> + <name>times new roman</name> + <name>palatino</name> + <name>georgia</name> + <name>baskerville</name> + <name>goudy</name> + <name>fantasy</name> + <name>cursive</name> + <name>ITC Stone Serif</name> + </nameset> + <fileset> + <file>DroidSerif-Regular.ttf</file> + <file>DroidSerif-Bold.ttf</file> + <file>DroidSerif-Italic.ttf</file> + <file>DroidSerif-BoldItalic.ttf</file> + </fileset> + </family> + + <family> + <nameset> + <name>monospace</name> + <name>courier</name> + <name>courier new</name> + <name>monaco</name> + </nameset> + <fileset> + <file>DroidSansMono.ttf</file> + </fileset> + </family> + +</familyset> diff --git a/data/fonts/vendor_fonts.xml b/data/fonts/vendor_fonts.xml new file mode 100644 index 000000000000..fe51fd27037e --- /dev/null +++ b/data/fonts/vendor_fonts.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Vendor-provided fallback fonts + + This file can be edited to add references to fonts that are not installed or referenced in the + default system. The file should then be placed in /vendor/etc/fallback_fonts.xml. + + For example, vendors might want to build configurations for locales that are + better served by fonts which either handle glyphs not supported in the default fonts or which + handle these glyphs differently than the default fallback fonts. + Each entry in this list is a "family", which consists of a list of "files" + (the filenames for that family). The files objects are + provided in the order of the styles supported for that family: regular, bold, italic, and + bold-italic. Only providing one font means that all styles will be rendered with that font. + Providing two means that these two fonts will render regular and bold fonts (italics will + be mapped to these two fonts). + + There is also an optional "order" attribute on the Family tag. This specifies the index at + which that family of fonts should be inserted in the fallback font list, where the + default fallback fonts on the system (in /system/etc/fallback_fonts.xml) start at index 0. + If no 'order' attribute is supplied, that family will be inserted either at the end of the + current fallback list (if no order was supplied for any previous family in this file) or + after the previous family (if there was an order specified previously). Typically, vendors + may want to supply an order for the first family that puts this set of fonts at the appropriate + place in the overall fallback fonts. The order of this list determines which fallback font + will be used to support any glyphs that are not handled by the default system fonts. + + The sample configuration below is an example of how one might provide two families of fonts + that get inserted at the first and second (0 and 1) position in the overall fallback fonts. + + See /system/etc/system_fonts.xml and /system/etc/fallback_fonts.xml for more information + and to understand the order in which the default system fonts are loaded and structured for + lookup. +--> + +<!-- Sample fallback font additions to the default fallback list. These fonts will be added + to the top two positions of the fallback list, since the first has an order of 0. --> +<!-- +<familyset> + <family order="0"> + <fileset> + <file>MyFont.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>MyOtherFont.ttf</file> + </fileset> + </family> +</familyset> +-->
\ No newline at end of file diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp index 0d0d6c20967e..20913816b362 100644 --- a/media/libstagefright/XINGSeeker.cpp +++ b/media/libstagefright/XINGSeeker.cpp @@ -24,8 +24,8 @@ namespace android { static bool parse_xing_header( const sp<DataSource> &source, off64_t first_frame_pos, int32_t *frame_number = NULL, int32_t *byte_number = NULL, - char *table_of_contents = NULL, int32_t *quality_indicator = NULL, - int64_t *duration = NULL); + unsigned char *table_of_contents = NULL, + int32_t *quality_indicator = NULL, int64_t *duration = NULL); // static sp<XINGSeeker> XINGSeeker::CreateFromSource( @@ -94,7 +94,7 @@ bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { static bool parse_xing_header( const sp<DataSource> &source, off64_t first_frame_pos, int32_t *frame_number, int32_t *byte_number, - char *table_of_contents, int32_t *quality_indicator, + unsigned char *table_of_contents, int32_t *quality_indicator, int64_t *duration) { if (frame_number) { *frame_number = 0; diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 8ecc17ce5580..ca61b3d2cc5f 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -36,11 +36,10 @@ #include <ctype.h> #include <openssl/aes.h> +#include <openssl/md5.h> namespace android { -const int64_t LiveSession::kMaxPlaylistAgeUs = 15000000ll; - LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid) : mFlags(flags), mUIDValid(uidValid), @@ -59,7 +58,8 @@ LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid) mDurationUs(-1), mSeekDone(false), mDisconnectPending(false), - mMonitorQueueGeneration(0) { + mMonitorQueueGeneration(0), + mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) { if (mUIDValid) { mHTTPDataSource->setUID(mUID); } @@ -175,7 +175,8 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mMasterURL = url; - sp<M3UParser> playlist = fetchPlaylist(url.c_str()); + bool dummy; + sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &dummy); if (playlist == NULL) { LOGE("unable to fetch master playlist '%s'.", url.c_str()); @@ -289,7 +290,9 @@ status_t LiveSession::fetchFile(const char *url, sp<ABuffer> *out) { return OK; } -sp<M3UParser> LiveSession::fetchPlaylist(const char *url) { +sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) { + *unchanged = false; + sp<ABuffer> buffer; status_t err = fetchFile(url, &buffer); @@ -297,6 +300,38 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url) { return NULL; } + // MD5 functionality is not available on the simulator, treat all + // playlists as changed. + +#if defined(HAVE_ANDROID_OS) + uint8_t hash[16]; + + MD5_CTX m; + MD5_Init(&m); + MD5_Update(&m, buffer->data(), buffer->size()); + + MD5_Final(hash, &m); + + if (mPlaylist != NULL && !memcmp(hash, mPlaylistHash, 16)) { + // playlist unchanged + + if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) { + mRefreshState = (RefreshState)(mRefreshState + 1); + } + + *unchanged = true; + + LOGV("Playlist unchanged, refresh state is now %d", + (int)mRefreshState); + + return NULL; + } + + memcpy(mPlaylistHash, hash, sizeof(hash)); + + mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY; +#endif + sp<M3UParser> playlist = new M3UParser(url, buffer->data(), buffer->size()); @@ -384,6 +419,63 @@ size_t LiveSession::getBandwidthIndex() { return index; } +bool LiveSession::timeToRefreshPlaylist(int64_t nowUs) const { + if (mPlaylist == NULL) { + CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY); + return true; + } + + int32_t targetDurationSecs; + CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs)); + + int64_t targetDurationUs = targetDurationSecs * 1000000ll; + + int64_t minPlaylistAgeUs; + + switch (mRefreshState) { + case INITIAL_MINIMUM_RELOAD_DELAY: + { + size_t n = mPlaylist->size(); + if (n > 0) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + minPlaylistAgeUs = itemDurationUs; + break; + } + + // fall through + } + + case FIRST_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = targetDurationUs / 2; + break; + } + + case SECOND_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = (targetDurationUs * 3) / 2; + break; + } + + case THIRD_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = targetDurationUs * 3; + break; + } + + default: + TRESPASS(); + break; + } + + return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs; +} + void LiveSession::onDownloadNext() { size_t bandwidthIndex = getBandwidthIndex(); @@ -392,8 +484,7 @@ rinse_repeat: if (mLastPlaylistFetchTimeUs < 0 || (ssize_t)bandwidthIndex != mPrevBandwidthIndex - || (!mPlaylist->isComplete() - && mLastPlaylistFetchTimeUs + kMaxPlaylistAgeUs <= nowUs)) { + || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) { AString url; if (mBandwidthItems.size() > 0) { url = mBandwidthItems.editItemAt(bandwidthIndex).mURI; @@ -403,11 +494,19 @@ rinse_repeat: bool firstTime = (mPlaylist == NULL); - mPlaylist = fetchPlaylist(url.c_str()); - if (mPlaylist == NULL) { - LOGE("failed to load playlist at url '%s'", url.c_str()); - mDataSource->queueEOS(ERROR_IO); - return; + bool unchanged; + sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged); + if (playlist == NULL) { + if (unchanged) { + // We succeeded in fetching the playlist, but it was + // unchanged from the last time we tried. + } else { + LOGE("failed to load playlist at url '%s'", url.c_str()); + mDataSource->queueEOS(ERROR_IO); + return; + } + } else { + mPlaylist = playlist; } if (firstTime) { diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h index 188ef5e605b3..116ed0e04280 100644 --- a/media/libstagefright/include/LiveSession.h +++ b/media/libstagefright/include/LiveSession.h @@ -62,8 +62,6 @@ private: kMaxNumRetries = 5, }; - static const int64_t kMaxPlaylistAgeUs; - enum { kWhatConnect = 'conn', kWhatDisconnect = 'disc', @@ -106,6 +104,16 @@ private: int32_t mMonitorQueueGeneration; + enum RefreshState { + INITIAL_MINIMUM_RELOAD_DELAY, + FIRST_UNCHANGED_RELOAD_ATTEMPT, + SECOND_UNCHANGED_RELOAD_ATTEMPT, + THIRD_UNCHANGED_RELOAD_ATTEMPT + }; + RefreshState mRefreshState; + + uint8_t mPlaylistHash[16]; + void onConnect(const sp<AMessage> &msg); void onDisconnect(); void onDownloadNext(); @@ -113,7 +121,7 @@ private: void onSeek(const sp<AMessage> &msg); status_t fetchFile(const char *url, sp<ABuffer> *out); - sp<M3UParser> fetchPlaylist(const char *url); + sp<M3UParser> fetchPlaylist(const char *url, bool *unchanged); size_t getBandwidthIndex(); status_t decryptBuffer( @@ -121,6 +129,8 @@ private: void postMonitorQueue(int64_t delayUs = 0); + bool timeToRefreshPlaylist(int64_t nowUs) const; + static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *); DISALLOW_EVIL_CONSTRUCTORS(LiveSession); diff --git a/media/libstagefright/include/XINGSeeker.h b/media/libstagefright/include/XINGSeeker.h index d5a484e0e2b4..ec5bd9b0ac94 100644 --- a/media/libstagefright/include/XINGSeeker.h +++ b/media/libstagefright/include/XINGSeeker.h @@ -37,7 +37,7 @@ private: int32_t mSizeBytes; // TOC entries in XING header. Skip the first one since it's always 0. - char mTableOfContents[99]; + unsigned char mTableOfContents[99]; XINGSeeker(); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 168b894da848..e9e66cb9d164 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -87,8 +87,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -109,6 +111,8 @@ class BackupManagerService extends IBackupManager.Stub { // Name and current contents version of the full-backup manifest file static final String BACKUP_MANIFEST_FILENAME = "_manifest"; static final int BACKUP_MANIFEST_VERSION = 1; + static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n"; + static final int BACKUP_FILE_VERSION = 1; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. @@ -1791,16 +1795,42 @@ class BackupManagerService extends IBackupManager.Stub { } } - // Set up the compression stage FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor()); + + // Set up the compression stage Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); DeflaterOutputStream out = new DeflaterOutputStream(ofstream, deflater, true); - // !!! TODO: if using encryption, set up the encryption stage - // and emit the tar header stating the password salt. - PackageInfo pkg = null; try { + + // !!! TODO: if using encryption, set up the encryption stage + // and emit the tar header stating the password salt. + + // Write the global file header. All strings are UTF-8 encoded; lines end + // with a '\n' byte. Actual backup data begins immediately following the + // final '\n'. + // + // line 1: "ANDROID BACKUP" + // line 2: backup file format version, currently "1" + // line 3: compressed? "0" if not compressed, "1" if compressed. + // line 4: encryption salt? "-" if not encrypted, otherwise this + // line contains the encryption salt with which the user- + // supplied password is to be expanded, in hexadecimal. + StringBuffer headerbuf = new StringBuffer(256); + // !!! TODO: programmatically build the compressed / encryption salt fields + headerbuf.append(BACKUP_FILE_HEADER_MAGIC); + headerbuf.append("1\n1\n-\n"); + + try { + byte[] header = headerbuf.toString().getBytes("UTF-8"); + ofstream.write(header); + } catch (Exception e) { + // Should never happen! + Slog.e(TAG, "Unable to emit archive header", e); + return; + } + // Now back up the app data via the agent mechanism int N = packagesToBackup.size(); for (int i = 0; i < N; i++) { @@ -2176,7 +2206,46 @@ class BackupManagerService extends IBackupManager.Stub { mBytes = 0; byte[] buffer = new byte[32 * 1024]; FileInputStream rawInStream = new FileInputStream(mInputFile.getFileDescriptor()); - InflaterInputStream in = new InflaterInputStream(rawInStream); + + // First, parse out the unencrypted/uncompressed header + boolean compressed = false; + boolean encrypted = false; + final InputStream in; + + boolean okay = false; + final int headerLen = BACKUP_FILE_HEADER_MAGIC.length(); + byte[] streamHeader = new byte[headerLen]; + try { + int got; + if ((got = rawInStream.read(streamHeader, 0, headerLen)) == headerLen) { + byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes("UTF-8"); + if (Arrays.equals(magicBytes, streamHeader)) { + // okay, header looks good. now parse out the rest of the fields. + String s = readHeaderLine(rawInStream); + if (Integer.parseInt(s) == BACKUP_FILE_VERSION) { + // okay, it's a version we recognize + s = readHeaderLine(rawInStream); + compressed = (Integer.parseInt(s) != 0); + s = readHeaderLine(rawInStream); + if (!s.startsWith("-")) { + encrypted = true; + // TODO: parse out the salt here and process with the user pw + } + okay = true; + } else Slog.e(TAG, "Wrong header version: " + s); + } else Slog.e(TAG, "Didn't read the right header magic"); + } else Slog.e(TAG, "Only read " + got + " bytes of header"); + } catch (NumberFormatException e) { + Slog.e(TAG, "Can't parse restore data header"); + } + + if (!okay) { + Slog.e(TAG, "Invalid restore data; aborting."); + return; + } + + // okay, use the right stream layer based on compression + in = (compressed) ? new InflaterInputStream(rawInStream) : rawInStream; boolean didRestore; do { @@ -2184,6 +2253,8 @@ class BackupManagerService extends IBackupManager.Stub { } while (didRestore); if (DEBUG) Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes); + } catch (IOException e) { + Slog.e(TAG, "Unable to read restore input"); } finally { tearDownPipes(); tearDownAgent(mTargetApp); @@ -2207,6 +2278,16 @@ class BackupManagerService extends IBackupManager.Stub { } } + String readHeaderLine(InputStream in) throws IOException { + int c; + StringBuffer buffer = new StringBuffer(80); + while ((c = in.read()) >= 0) { + if (c == '\n') break; // consume and discard the newlines + buffer.append((char)c); + } + return buffer.toString(); + } + boolean restoreOneFile(InputStream instream, byte[] buffer) { FileMetadata info; try { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 5f0922ec4f09..711255327499 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -35,8 +35,8 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiStateMachine; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiWatchdogStateMachine; import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WifiWatchdogService; import android.net.wifi.WpsConfiguration; import android.net.wifi.WpsResult; import android.net.ConnectivityManager; @@ -343,7 +343,7 @@ public class WifiService extends IWifiManager.Stub { * Protected by mWifiStateTracker lock. */ private final WorkSource mTmpWorkSource = new WorkSource(); - private WifiWatchdogService mWifiWatchdogService; + private WifiWatchdogStateMachine mWifiWatchdogStateMachine; WifiService(Context context) { mContext = context; @@ -434,8 +434,9 @@ public class WifiService extends IWifiManager.Stub { (wifiEnabled ? "enabled" : "disabled")); setWifiEnabled(wifiEnabled); - //TODO: as part of WWS refactor, create only when needed - mWifiWatchdogService = new WifiWatchdogService(mContext); + mWifiWatchdogStateMachine = WifiWatchdogStateMachine. + makeWifiWatchdogStateMachine(mContext); + } private boolean testAndClearWifiSavedState() { @@ -1162,8 +1163,8 @@ public class WifiService extends IWifiManager.Stub { mLocks.dump(pw); pw.println(); - pw.println("WifiWatchdogService dump"); - mWifiWatchdogService.dump(pw); + pw.println("WifiWatchdogStateMachine dump"); + mWifiWatchdogStateMachine.dump(pw); } private class WifiLock extends DeathRecipient { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b0881a4a9403..680814cf230a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1551,8 +1551,18 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) * Dump SurfaceFlinger global state */ - snprintf(buffer, SIZE, "SurfaceFlinger global state\n"); + snprintf(buffer, SIZE, "SurfaceFlinger global state:\n"); result.append(buffer); + + const GLExtensions& extensions(GLExtensions::getInstance()); + snprintf(buffer, SIZE, "GLES: %s, %s, %s\n", + extensions.getVendor(), + extensions.getRenderer(), + extensions.getVersion()); + result.append(buffer); + snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension()); + result.append(buffer); + mWormholeRegion.dump(result, "WormholeRegion"); const DisplayHardware& hw(graphicPlane(0).displayHardware()); snprintf(buffer, SIZE, diff --git a/wifi/java/android/net/wifi/WifiWatchdogService.java b/wifi/java/android/net/wifi/WifiWatchdogService.java deleted file mode 100644 index bce4b3ad5f37..000000000000 --- a/wifi/java/android/net/wifi/WifiWatchdogService.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.wifi; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.ContentObserver; -import android.net.ConnectivityManager; -import android.net.DnsPinger; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Slog; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.HashSet; -import java.util.List; -import java.util.Scanner; - -/** - * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi - * network with multiple access points. After the framework successfully - * connects to an access point, the watchdog verifies connectivity by 'pinging' - * the configured DNS server using {@link DnsPinger}. - * <p> - * On DNS check failure, the BSSID is blacklisted if it is reasonably likely - * that another AP might have internet access; otherwise the SSID is disabled. - * <p> - * On DNS success, the WatchdogService initiates a walled garden check via an - * http get. A browser windows is activated if a walled garden is detected. - * - * @hide - */ -public class WifiWatchdogService { - - private static final String WWS_TAG = "WifiWatchdogService"; - - private static final boolean VDBG = true; - private static final boolean DBG = true; - - // Used for verbose logging - private String mDNSCheckLogStr; - - private Context mContext; - private ContentResolver mContentResolver; - private WifiManager mWifiManager; - - private WifiWatchdogHandler mHandler; - - private DnsPinger mDnsPinger; - - private IntentFilter mIntentFilter; - private BroadcastReceiver mBroadcastReceiver; - private boolean mBroadcastsEnabled; - - private static final int WIFI_SIGNAL_LEVELS = 4; - - /** - * Low signal is defined as less than or equal to cut off - */ - private static final int LOW_SIGNAL_CUTOFF = 0; - - private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000; - private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000; - private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000; - - private static final int MAX_CHECKS_PER_SSID = 9; - private static final int NUM_DNS_PINGS = 7; - private static double MIN_RESPONSE_RATE = 0.50; - - // TODO : Adjust multiple DNS downward to 250 on repeated failure - // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250; - - private static final int DNS_PING_TIMEOUT_MS = 800; - private static final long DNS_PING_INTERVAL = 250; - - private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000; - - private Status mStatus = new Status(); - - private static class Status { - String bssid = ""; - String ssid = ""; - - HashSet<String> allBssids = new HashSet<String>(); - int numFullDNSchecks = 0; - - long lastSingleCheckTime = -24 * 60 * 60 * 1000; - long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000; - - WatchdogState state = WatchdogState.INACTIVE; - - // Info for dns check - int dnsCheckTries = 0; - int dnsCheckSuccesses = 0; - - public int signal = -200; - - } - - private enum WatchdogState { - /** - * Full DNS check in progress - */ - DNS_FULL_CHECK, - - /** - * Walled Garden detected, will pop up browser next round. - */ - WALLED_GARDEN_DETECTED, - - /** - * DNS failed, will blacklist/disable AP next round - */ - DNS_CHECK_FAILURE, - - /** - * Online or displaying walled garden auth page - */ - CHECKS_COMPLETE, - - /** - * Watchdog idle, network has been blacklisted or received disconnect - * msg - */ - INACTIVE, - - BLACKLISTED_AP - } - - public WifiWatchdogService(Context context) { - mContext = context; - mContentResolver = context.getContentResolver(); - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context, - ConnectivityManager.TYPE_WIFI); - - HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread"); - handlerThread.start(); - mHandler = new WifiWatchdogHandler(handlerThread.getLooper()); - - setupNetworkReceiver(); - - // The content observer to listen needs a handler, which createThread - // creates - registerForSettingsChanges(); - - // Start things off - if (isWatchdogEnabled()) { - mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT); - } - } - - /** - * - */ - private void setupNetworkReceiver() { - mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - mHandler.sendMessage(mHandler.obtainMessage( - WifiWatchdogHandler.MESSAGE_NETWORK_EVENT, - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) - )); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT); - } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE); - } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - mHandler.sendMessage(mHandler.obtainMessage( - WifiWatchdogHandler.WIFI_STATE_CHANGE, - intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4))); - } - } - }; - - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - } - - /** - * Observes the watchdog on/off setting, and takes action when changed. - */ - private void registerForSettingsChanges() { - ContentObserver contentObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT)); - } - }; - - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), - false, contentObserver); - } - - private void handleNewConnection() { - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - String newSsid = wifiInfo.getSSID(); - String newBssid = wifiInfo.getBSSID(); - - if (VDBG) { - Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)", - mStatus.ssid, mStatus.bssid, newSsid, newBssid)); - } - - if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) { - return; - } - - if (!TextUtils.equals(mStatus.ssid, newSsid)) { - mStatus = new Status(); - mStatus.ssid = newSsid; - } - - mStatus.bssid = newBssid; - mStatus.allBssids.add(newBssid); - mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS); - - initDnsFullCheck(); - } - - public void updateRssi() { - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) || - !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) { - return; - } - - mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS); - } - - /** - * Single step in state machine - */ - private void handleStateStep() { - // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state); - - switch (mStatus.state) { - case DNS_FULL_CHECK: - if (VDBG) { - Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr); - } - - long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), - DNS_PING_TIMEOUT_MS); - - mStatus.dnsCheckTries++; - if (pingResponseTime >= 0) - mStatus.dnsCheckSuccesses++; - - if (DBG) { - if (pingResponseTime >= 0) { - mDNSCheckLogStr += " | " + pingResponseTime; - } else { - mDNSCheckLogStr += " | " + "x"; - } - } - - switch (currentDnsCheckStatus()) { - case SUCCESS: - if (DBG) { - Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success"); - } - doWalledGardenCheck(); - break; - case FAILURE: - if (DBG) { - Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure"); - } - mStatus.state = WatchdogState.DNS_CHECK_FAILURE; - break; - case INCOMPLETE: - // Taking no action - break; - } - break; - case DNS_CHECK_FAILURE: - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - if (!mStatus.ssid.equals(wifiInfo.getSSID()) || - !mStatus.bssid.equals(wifiInfo.getBSSID())) { - Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!"); - mStatus.state = WatchdogState.INACTIVE; - break; - } - - if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() || - mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) { - disableAP(wifiInfo); - } else { - blacklistAP(); - } - break; - case WALLED_GARDEN_DETECTED: - popUpBrowser(); - mStatus.state = WatchdogState.CHECKS_COMPLETE; - break; - case BLACKLISTED_AP: - WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo(); - if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) { - Slog.d(WWS_TAG, - "handleState::BlacklistedAP - offline, but didn't get disconnect!"); - mStatus.state = WatchdogState.INACTIVE; - break; - } - if (mStatus.bssid.equals(wifiInfo2.getBSSID())) { - Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid"); - if (!handleSingleDnsCheck()) { - disableAP(wifiInfo2); - break; - } - } - - Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection"); - handleNewConnection(); - break; - } - } - - private void doWalledGardenCheck() { - if (!isWalledGardenTestEnabled()) { - if (VDBG) - Slog.v(WWS_TAG, "Skipping walled garden check - disabled"); - mStatus.state = WatchdogState.CHECKS_COMPLETE; - return; - } - long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL, - mStatus.lastWalledGardenCheckTime); - if (waitTime > 0) { - if (VDBG) { - Slog.v(WWS_TAG, "Skipping walled garden check - wait " + - waitTime + " ms."); - } - mStatus.state = WatchdogState.CHECKS_COMPLETE; - return; - } - - mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime(); - if (isWalledGardenConnection()) { - if (DBG) - Slog.d(WWS_TAG, - "Walled garden test complete - walled garden detected"); - mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED; - } else { - if (DBG) - Slog.d(WWS_TAG, "Walled garden test complete - online"); - mStatus.state = WatchdogState.CHECKS_COMPLETE; - } - } - - private boolean handleSingleDnsCheck() { - mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime(); - long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), - DNS_PING_TIMEOUT_MS); - if (DBG) { - Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime); - } - if (responseTime < 0) { - return false; - } - return true; - - } - - /** - * @return Delay in MS before next single DNS check can proceed. - */ - private long timeToNextScheduledDNSCheck() { - if (mStatus.signal > LOW_SIGNAL_CUTOFF) { - return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime); - } else { - return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime); - } - } - - /** - * Helper to return wait time left given a min interval and last run - * - * @param interval minimum wait interval - * @param lastTime last time action was performed in - * SystemClock.elapsedRealtime() - * @return non negative time to wait - */ - private static long waitTime(long interval, long lastTime) { - long wait = interval + lastTime - SystemClock.elapsedRealtime(); - return wait > 0 ? wait : 0; - } - - private void popUpBrowser() { - Uri uri = Uri.parse("http://www.google.com"); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); - } - - private void disableAP(WifiInfo info) { - // TODO : Unban networks if they had low signal ? - Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s]. " + - "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid, - mStatus.numFullDNSchecks, mStatus.allBssids.size())); - mWifiManager.disableNetwork(info.getNetworkId()); - mStatus.state = WatchdogState.INACTIVE; - } - - private void blacklistAP() { - Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s]. " + - "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid, - mStatus.numFullDNSchecks, mStatus.allBssids.size())); - - mWifiManager.addToBlacklist(mStatus.bssid); - mWifiManager.reassociate(); - mStatus.state = WatchdogState.BLACKLISTED_AP; - } - - /** - * Checks the scan for new BBIDs using current mSsid - */ - private void updateBssids() { - String curSsid = mStatus.ssid; - HashSet<String> bssids = mStatus.allBssids; - List<ScanResult> results = mWifiManager.getScanResults(); - int oldNumBssids = bssids.size(); - - if (results == null) { - if (VDBG) { - Slog.v(WWS_TAG, "updateBssids: Got null scan results!"); - } - return; - } - - for (ScanResult result : results) { - if (result != null && curSsid.equals(result.SSID)) - bssids.add(result.BSSID); - } - - // if (VDBG && bssids.size() - oldNumBssids > 0) { - // Slog.v(WWS_TAG, - // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s", - // bssids.size() - oldNumBssids, bssids.size(), curSsid)); - // } - } - - enum DnsCheckStatus { - SUCCESS, - FAILURE, - INCOMPLETE - } - - /** - * Computes the current results of the dns check, ends early if outcome is - * assured. - */ - private DnsCheckStatus currentDnsCheckStatus() { - /** - * After a full ping count, if we have more responses than this cutoff, - * the outcome is success; else it is 'failure'. - */ - double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS; - int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries; - - /** - * Our final success count will be at least this big, so we're - * guaranteed to succeed. - */ - if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) { - return DnsCheckStatus.SUCCESS; - } - - /** - * Our final count will be at most the current count plus the remaining - * pings - we're guaranteed to fail. - */ - if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) { - return DnsCheckStatus.FAILURE; - } - - return DnsCheckStatus.INCOMPLETE; - } - - private void initDnsFullCheck() { - if (DBG) { - Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime()); - } - mStatus.numFullDNSchecks++; - mStatus.dnsCheckSuccesses = 0; - mStatus.dnsCheckTries = 0; - mStatus.state = WatchdogState.DNS_FULL_CHECK; - - if (DBG) { - mDNSCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ", - mStatus.numFullDNSchecks, mDnsPinger.getDns(), - mStatus.ssid); - } - } - - /** - * DNS based detection techniques do not work at all hotspots. The one sure - * way to check a walled garden is to see if a URL fetch on a known address - * fetches the data we expect - */ - private boolean isWalledGardenConnection() { - InputStream in = null; - HttpURLConnection urlConnection = null; - try { - URL url = new URL(getWalledGardenUrl()); - urlConnection = (HttpURLConnection) url.openConnection(); - in = new BufferedInputStream(urlConnection.getInputStream()); - Scanner scanner = new Scanner(in); - if (scanner.findInLine(getWalledGardenPattern()) != null) { - return false; - } else { - return true; - } - } catch (IOException e) { - return false; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - if (urlConnection != null) - urlConnection.disconnect(); - } - } - - /** - * There is little logic inside this class, instead methods of the form - * "handle___" are called in the main {@link WifiWatchdogService}. - */ - private class WifiWatchdogHandler extends Handler { - /** - * Major network event, object is NetworkInfo - */ - static final int MESSAGE_NETWORK_EVENT = 1; - /** - * Change in settings, no object - */ - static final int MESSAGE_CONTEXT_EVENT = 2; - - /** - * Change in signal strength - */ - static final int RSSI_CHANGE_EVENT = 3; - static final int SCAN_RESULTS_AVAILABLE = 4; - - static final int WIFI_STATE_CHANGE = 5; - - /** - * Single step of state machine. One DNS check, or one WalledGarden - * check, or one external action. We separate out external actions to - * increase chance of detecting that a check failure is caused by change - * in network status. Messages should have an arg1 which to sync status - * messages. - */ - static final int CHECK_SEQUENCE_STEP = 10; - static final int SINGLE_DNS_CHECK = 11; - - /** - * @param looper - */ - public WifiWatchdogHandler(Looper looper) { - super(looper); - } - - boolean singleCheckQueued = false; - long queuedSingleDnsCheckArrival; - - /** - * Sends a singleDnsCheck message with shortest time - guards against - * multiple. - */ - private boolean queueSingleDnsCheck() { - long delay = timeToNextScheduledDNSCheck(); - long newArrival = delay + SystemClock.elapsedRealtime(); - if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival) - return true; - queuedSingleDnsCheckArrival = newArrival; - singleCheckQueued = true; - removeMessages(SINGLE_DNS_CHECK); - return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay); - } - - boolean checkSequenceQueued = false; - long queuedCheckSequenceArrival; - - /** - * Sends a state_machine_step message if the delay requested is lower - * than the current delay. - */ - private boolean sendCheckSequenceStep(long delay) { - long newArrival = delay + SystemClock.elapsedRealtime(); - if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival) - return true; - queuedCheckSequenceArrival = newArrival; - checkSequenceQueued = true; - removeMessages(CHECK_SEQUENCE_STEP); - return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case CHECK_SEQUENCE_STEP: - checkSequenceQueued = false; - handleStateStep(); - if (mStatus.state == WatchdogState.CHECKS_COMPLETE) { - queueSingleDnsCheck(); - } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) { - sendCheckSequenceStep(DNS_PING_INTERVAL); - } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) { - sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL); - } else if (mStatus.state != WatchdogState.INACTIVE) { - sendCheckSequenceStep(0); - } - return; - case MESSAGE_NETWORK_EVENT: - if (!mBroadcastsEnabled) { - Slog.e(WWS_TAG, - "MessageNetworkEvent - WatchdogService not enabled... returning"); - return; - } - NetworkInfo info = (NetworkInfo) msg.obj; - switch (info.getState()) { - case DISCONNECTED: - mStatus.state = WatchdogState.INACTIVE; - return; - case CONNECTED: - handleNewConnection(); - sendCheckSequenceStep(0); - } - return; - case SINGLE_DNS_CHECK: - singleCheckQueued = false; - if (mStatus.state != WatchdogState.CHECKS_COMPLETE) { - Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state); - break; - } - - if (!handleSingleDnsCheck()) { - initDnsFullCheck(); - sendCheckSequenceStep(0); - } else { - queueSingleDnsCheck(); - } - - break; - case RSSI_CHANGE_EVENT: - updateRssi(); - if (mStatus.state == WatchdogState.CHECKS_COMPLETE) - queueSingleDnsCheck(); - break; - case SCAN_RESULTS_AVAILABLE: - updateBssids(); - break; - case WIFI_STATE_CHANGE: - if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { - Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState"); - mStatus = new Status(); - } - break; - case MESSAGE_CONTEXT_EVENT: - if (isWatchdogEnabled() && !mBroadcastsEnabled) { - mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); - mBroadcastsEnabled = true; - Slog.i(WWS_TAG, "WifiWatchdogService enabled"); - } else if (!isWatchdogEnabled() && mBroadcastsEnabled) { - mContext.unregisterReceiver(mBroadcastReceiver); - removeMessages(SINGLE_DNS_CHECK); - removeMessages(CHECK_SEQUENCE_STEP); - mBroadcastsEnabled = false; - Slog.i(WWS_TAG, "WifiWatchdogService disabled"); - } - break; - } - } - } - - public void dump(PrintWriter pw) { - pw.print("WatchdogStatus: "); - pw.print("State " + mStatus.state); - pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]"); - pw.print("checkCount " + mStatus.numFullDNSchecks); - pw.println(", bssids: " + mStatus.allBssids); - pw.print(", hasCheckMessages? " + - mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP)); - pw.println(" hasSingleCheckMessages? " + - mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK)); - pw.println("DNS check log str: " + mDNSCheckLogStr); - pw.println("lastSingleCheck: " + mStatus.lastSingleCheckTime); - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED - */ - private Boolean isWalledGardenTestEnabled() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL - */ - private String getWalledGardenUrl() { - String url = Settings.Secure.getString(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL); - if (TextUtils.isEmpty(url)) - return "http://www.google.com/"; - return url; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN - */ - private String getWalledGardenPattern() { - String pattern = Settings.Secure.getString(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN); - if (TextUtils.isEmpty(pattern)) - return "<title>.*Google.*</title>"; - return pattern; - } - - /** - * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON - */ - private boolean isWatchdogEnabled() { - return Settings.Secure.getInt(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1; - } -} diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java new file mode 100644 index 000000000000..0eb73b7e8a95 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.DnsPinger; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; + +/** + * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi + * network with multiple access points. After the framework successfully + * connects to an access point, the watchdog verifies connectivity by 'pinging' + * the configured DNS server using {@link DnsPinger}. + * <p> + * On DNS check failure, the BSSID is blacklisted if it is reasonably likely + * that another AP might have internet access; otherwise the SSID is disabled. + * <p> + * On DNS success, the WatchdogService initiates a walled garden check via an + * http get. A browser window is activated if a walled garden is detected. + * + * @hide + */ +public class WifiWatchdogStateMachine extends StateMachine { + + private static final boolean VDBG = false; + private static final boolean DBG = true; + private static final String WWSM_TAG = "WifiWatchdogStateMachine"; + + private static final int WIFI_SIGNAL_LEVELS = 4; + /** + * Low signal is defined as less than or equal to cut off + */ + private static final int LOW_SIGNAL_CUTOFF = 1; + + private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL_MS = 2 * 60 * 1000; + private static final long MIN_SINGLE_DNS_CHECK_INTERVAL_MS = 10 * 60 * 1000; + private static final long MIN_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; + + private static final int MAX_CHECKS_PER_SSID = 7; + private static final int NUM_DNS_PINGS = 5; + private static final double MIN_DNS_RESPONSE_RATE = 0.50; + + private static final int DNS_PING_TIMEOUT_MS = 800; + private static final long DNS_PING_INTERVAL_MS = 100; + + private static final long BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000; + + private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; + + /** + * Indicates the enable setting of WWS may have changed + */ + private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; + + /** + * Indicates the wifi network state has changed. Passed w/ original intent + * which has a non-null networkInfo object + */ + private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; + /** + * Indicates the signal has changed. Passed with arg1 + * {@link #mNetEventCounter} and arg2 [raw signal strength] + */ + private static final int EVENT_RSSI_CHANGE = BASE + 3; + private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4; + private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; + + private static final int MESSAGE_CHECK_STEP = BASE + 100; + private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 101; + private static final int MESSAGE_HANDLE_BAD_AP = BASE + 102; + /** + * arg1 == mOnlineWatchState.checkCount + */ + private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 103; + private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 104; + + private Context mContext; + private ContentResolver mContentResolver; + private WifiManager mWifiManager; + private DnsPinger mDnsPinger; + private IntentFilter mIntentFilter; + private BroadcastReceiver mBroadcastReceiver; + + private DefaultState mDefaultState = new DefaultState(); + private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); + private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); + private NotConnectedState mNotConnectedState = new NotConnectedState(); + private ConnectedState mConnectedState = new ConnectedState(); + private DnsCheckingState mDnsCheckingState = new DnsCheckingState(); + private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); + private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState(); + private WalledGardenState mWalledGardenState = new WalledGardenState(); + private BlacklistedApState mBlacklistedApState = new BlacklistedApState(); + + /** + * The {@link WifiInfo} object passed to WWSM on network broadcasts + */ + private WifiInfo mInitialConnInfo; + private int mNetEventCounter = 0; + + /** + * Currently maintained but not used, TODO + */ + private HashSet<String> mBssids = new HashSet<String>(); + private int mNumFullDNSchecks = 0; + + private Long mLastWalledGardenCheckTime = null; + + /** + * This is set by the blacklisted state and reset when connected to a new AP. + * It triggers a disableNetwork call if a DNS check fails. + */ + public boolean mDisableAPNextFailure = false; + + /** + * STATE MAP + * Default + * / \ + * Disabled Enabled + * / \ + * Disconnected Connected + * /---------\ + * (all other states) + */ + private WifiWatchdogStateMachine(Context context) { + super(WWSM_TAG); + mContext = context; + mContentResolver = context.getContentResolver(); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context, + ConnectivityManager.TYPE_WIFI); + + setupNetworkReceiver(); + + // The content observer to listen needs a handler + registerForSettingsChanges(); + addState(mDefaultState); + addState(mWatchdogDisabledState, mDefaultState); + addState(mWatchdogEnabledState, mDefaultState); + addState(mNotConnectedState, mWatchdogEnabledState); + addState(mConnectedState, mWatchdogEnabledState); + addState(mDnsCheckingState, mConnectedState); + addState(mDnsCheckFailureState, mConnectedState); + addState(mWalledGardenState, mConnectedState); + addState(mBlacklistedApState, mConnectedState); + addState(mOnlineWatchState, mConnectedState); + + setInitialState(mWatchdogDisabledState); + } + + public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) { + WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); + wwsm.start(); + wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED); + return wwsm; + } + + /** + * + */ + private void setupNetworkReceiver() { + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); + } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter, + intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget(); + } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + sendMessage(EVENT_SCAN_RESULTS_AVAILABLE); + } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, + intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN)); + } + } + }; + + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + } + + /** + * Observes the watchdog on/off setting, and takes action when changed. + */ + private void registerForSettingsChanges() { + ContentObserver contentObserver = new ContentObserver(this.getHandler()) { + @Override + public void onChange(boolean selfChange) { + sendMessage(EVENT_WATCHDOG_TOGGLED); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), + false, contentObserver); + } + + /** + * DNS based detection techniques do not work at all hotspots. The one sure + * way to check a walled garden is to see if a URL fetch on a known address + * fetches the data we expect + */ + private boolean isWalledGardenConnection() { + InputStream in = null; + HttpURLConnection urlConnection = null; + try { + URL url = new URL(getWalledGardenUrl()); + urlConnection = (HttpURLConnection) url.openConnection(); + in = new BufferedInputStream(urlConnection.getInputStream()); + Scanner scanner = new Scanner(in); + if (scanner.findInLine(getWalledGardenPattern()) != null) { + return false; + } else { + return true; + } + } catch (IOException e) { + return false; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + if (urlConnection != null) + urlConnection.disconnect(); + } + } + + private boolean rssiStrengthAboveCutoff(int rssi) { + return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF; + } + + public void dump(PrintWriter pw) { + pw.print("WatchdogStatus: "); + pw.print("State " + getCurrentState()); + pw.println(", network [" + mInitialConnInfo + "]"); + pw.print("checkCount " + mNumFullDNSchecks); + pw.println(", bssids: " + mBssids); + pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime); + } + + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED + */ + private Boolean isWalledGardenTestEnabled() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1; + } + + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL + */ + private String getWalledGardenUrl() { + String url = Settings.Secure.getString(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL); + if (TextUtils.isEmpty(url)) + return "http://www.google.com/"; + return url; + } + + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN + */ + private String getWalledGardenPattern() { + String pattern = Settings.Secure.getString(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN); + if (TextUtils.isEmpty(pattern)) + return "<title>.*Google.*</title>"; + return pattern; + } + + /** + * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON + */ + private boolean isWatchdogEnabled() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1; + } + + + /** + * Helper to return wait time left given a min interval and last run + * + * @param interval minimum wait interval + * @param lastTime last time action was performed in + * SystemClock.elapsedRealtime(). Null if never. + * @return non negative time to wait + */ + private static long waitTime(long interval, Long lastTime) { + if (lastTime == null) + return 0; + long wait = interval + lastTime - SystemClock.elapsedRealtime(); + return wait > 0 ? wait : 0; + } + + private static String wifiInfoToStr(WifiInfo wifiInfo) { + if (wifiInfo == null) + return "null"; + return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")"; + } + + /** + * + */ + private void resetWatchdogState() { + mInitialConnInfo = null; + mDisableAPNextFailure = false; + mLastWalledGardenCheckTime = null; + mNumFullDNSchecks = 0; + mBssids.clear(); + } + + private void popUpBrowser() { + Uri uri = Uri.parse("http://www.google.com"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + + private void sendCheckStepMessage(long delay) { + sendMessageDelayed(obtainMessage(MESSAGE_CHECK_STEP, mNetEventCounter, 0), delay); + } + + class DefaultState extends State { + @Override + public boolean processMessage(Message msg) { + if (VDBG) { + Slog.v(WWSM_TAG, "Caught message " + msg.what + " in state " + + getCurrentState().getName()); + } + return HANDLED; + } + } + + class WatchdogDisabledState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_WATCHDOG_TOGGLED: + if (isWatchdogEnabled()) + transitionTo(mNotConnectedState); + return HANDLED; + } + return NOT_HANDLED; + } + } + + class WatchdogEnabledState extends State { + @Override + public void enter() { + resetWatchdogState(); + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); + Slog.i(WWSM_TAG, "WifiWatchdogService enabled"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_WATCHDOG_TOGGLED: + if (!isWatchdogEnabled()) + transitionTo(mWatchdogDisabledState); + return HANDLED; + case EVENT_NETWORK_STATE_CHANGE: + Intent stateChangeIntent = (Intent) msg.obj; + NetworkInfo networkInfo = (NetworkInfo) + stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + + switch (networkInfo.getState()) { + case CONNECTED: + // WifiInfo wifiInfo = (WifiInfo) + // stateChangeIntent + // .getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); + // TODO : Replace with above code when API is changed + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + if (wifiInfo == null) { + Slog.e(WWSM_TAG, "Connected --> WifiInfo object null!"); + return HANDLED; + } + + if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { + Slog.e(WWSM_TAG, "Received wifiInfo object with null elts: " + + wifiInfoToStr(wifiInfo)); + return HANDLED; + } + + initConnection(wifiInfo); + transitionTo(mDnsCheckingState); + mNetEventCounter++; + return HANDLED; + case DISCONNECTED: + case DISCONNECTING: + mNetEventCounter++; + transitionTo(mNotConnectedState); + return HANDLED; + } + return HANDLED; + case EVENT_WIFI_RADIO_STATE_CHANGE: + if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { + Slog.i(WWSM_TAG, "WifiStateDisabling -- Resetting WatchdogState"); + resetWatchdogState(); + mNetEventCounter++; + transitionTo(mNotConnectedState); + } + return HANDLED; + } + + return NOT_HANDLED; + } + + /** + * @param wifiInfo Info object with non-null ssid and bssid + */ + private void initConnection(WifiInfo wifiInfo) { + if (VDBG) { + Slog.v(WWSM_TAG, "Connected:: old " + wifiInfoToStr(mInitialConnInfo) + + " ==> new " + wifiInfoToStr(wifiInfo)); + } + + if (mInitialConnInfo == null || !wifiInfo.getSSID().equals(mInitialConnInfo.getSSID())) { + resetWatchdogState(); + } else if (!wifiInfo.getBSSID().equals(mInitialConnInfo.getBSSID())) { + mDisableAPNextFailure = false; + } + mInitialConnInfo = wifiInfo; + } + + @Override + public void exit() { + mContext.unregisterReceiver(mBroadcastReceiver); + Slog.i(WWSM_TAG, "WifiWatchdogService disabled"); + } + } + + class NotConnectedState extends State { + } + + class ConnectedState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_SCAN_RESULTS_AVAILABLE: + String curSsid = mInitialConnInfo.getSSID(); + List<ScanResult> results = mWifiManager.getScanResults(); + int oldNumBssids = mBssids.size(); + + if (results == null) { + if (DBG) { + Slog.d(WWSM_TAG, "updateBssids: Got null scan results!"); + } + return HANDLED; + } + + for (ScanResult result : results) { + if (result == null || result.SSID == null) { + if (VDBG) { + Slog.v(WWSM_TAG, "Received invalid scan result: " + result); + } + continue; + } + if (curSsid.equals(result.SSID)) + mBssids.add(result.BSSID); + } + return HANDLED; + } + return NOT_HANDLED; + } + + } + + class DnsCheckingState extends State { + int dnsCheckTries = 0; + int dnsCheckSuccesses = 0; + String dnsCheckLogStr = ""; + + @Override + public void enter() { + mNumFullDNSchecks++; + dnsCheckSuccesses = 0; + dnsCheckTries = 0; + if (DBG) { + Slog.d(WWSM_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime()); + dnsCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ", + mNumFullDNSchecks, mDnsPinger.getDns(), mInitialConnInfo.getSSID()); + } + + sendCheckStepMessage(0); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what != MESSAGE_CHECK_STEP) { + return NOT_HANDLED; + } + if (msg.arg1 != mNetEventCounter) { + Slog.d(WWSM_TAG, "Check step out of sync, ignoring..."); + return HANDLED; + } + + long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), + DNS_PING_TIMEOUT_MS); + + dnsCheckTries++; + if (pingResponseTime >= 0) + dnsCheckSuccesses++; + + if (DBG) { + if (pingResponseTime >= 0) { + dnsCheckLogStr += "|" + pingResponseTime; + } else { + dnsCheckLogStr += "|x"; + } + } + + if (VDBG) { + Slog.v(WWSM_TAG, dnsCheckLogStr); + } + + /** + * After a full ping count, if we have more responses than this + * cutoff, the outcome is success; else it is 'failure'. + */ + double pingResponseCutoff = MIN_DNS_RESPONSE_RATE * NUM_DNS_PINGS; + int remainingChecks = NUM_DNS_PINGS - dnsCheckTries; + + /** + * Our final success count will be at least this big, so we're + * guaranteed to succeed. + */ + if (dnsCheckSuccesses >= pingResponseCutoff) { + // DNS CHECKS OK, NOW WALLED GARDEN + if (DBG) { + Slog.d(WWSM_TAG, dnsCheckLogStr + "| SUCCESS"); + } + + if (!shouldCheckWalledGarden()) { + transitionTo(mOnlineWatchState); + return HANDLED; + } + + mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); + if (isWalledGardenConnection()) { + if (DBG) + Slog.d(WWSM_TAG, + "Walled garden test complete - walled garden detected"); + transitionTo(mWalledGardenState); + } else { + if (DBG) + Slog.d(WWSM_TAG, "Walled garden test complete - online"); + transitionTo(mOnlineWatchState); + } + return HANDLED; + } + + /** + * Our final count will be at most the current count plus the + * remaining pings - we're guaranteed to fail. + */ + if (remainingChecks + dnsCheckSuccesses < pingResponseCutoff) { + if (DBG) { + Slog.d(WWSM_TAG, dnsCheckLogStr + "| FAILURE"); + } + transitionTo(mDnsCheckFailureState); + return HANDLED; + } + + // Still in dns check step + sendCheckStepMessage(DNS_PING_INTERVAL_MS); + return HANDLED; + } + + private boolean shouldCheckWalledGarden() { + if (!isWalledGardenTestEnabled()) { + if (VDBG) + Slog.v(WWSM_TAG, "Skipping walled garden check - disabled"); + return false; + } + long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL_MS, + mLastWalledGardenCheckTime); + if (waitTime > 0) { + if (DBG) { + Slog.d(WWSM_TAG, "Skipping walled garden check - wait " + + waitTime + " ms."); + } + return false; + } + return true; + } + + } + + class OnlineWatchState extends State { + /** + * Signals a short-wait message is enqueued for the current 'guard' counter + */ + boolean unstableSignalChecks = false; + + /** + * The signal is unstable. We should enqueue a short-wait check, if one is enqueued + * already + */ + boolean signalUnstable = false; + + /** + * A monotonic counter to ensure that at most one check message will be processed from any + * set of check messages currently enqueued. Avoids duplicate checks when a low-signal + * event is observed. + */ + int checkGuard = 0; + Long lastCheckTime = null; + + @Override + public void enter() { + lastCheckTime = SystemClock.elapsedRealtime(); + signalUnstable = false; + checkGuard++; + unstableSignalChecks = false; + triggerSingleDnsCheck(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_RSSI_CHANGE: + if (msg.arg1 != mNetEventCounter) { + if (DBG) { + Slog.d(WWSM_TAG, "Rssi change message out of sync, ignoring"); + } + return HANDLED; + } + int newRssi = msg.arg2; + signalUnstable = !rssiStrengthAboveCutoff(newRssi); + if (VDBG) { + Slog.v(WWSM_TAG, "OnlineWatchState:: new rssi " + newRssi + " --> level " + + WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS)); + } + + if (signalUnstable && !unstableSignalChecks) { + if (VDBG) { + Slog.v(WWSM_TAG, "Sending triggered check msg"); + } + triggerSingleDnsCheck(); + } + return HANDLED; + case MESSAGE_SINGLE_DNS_CHECK: + if (msg.arg1 != checkGuard) { + if (VDBG) { + Slog.v(WWSM_TAG, "Single check msg out of sync, ignoring."); + } + return HANDLED; + } + lastCheckTime = SystemClock.elapsedRealtime(); + long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), + DNS_PING_TIMEOUT_MS); + if (responseTime >= 0) { + if (VDBG) { + Slog.v(WWSM_TAG, "Ran a single DNS ping. Response time: " + + responseTime); + } + + checkGuard++; + unstableSignalChecks = false; + triggerSingleDnsCheck(); + } else { + if (DBG) { + Slog.d(WWSM_TAG, "Single dns ping failure. Starting full checks."); + } + transitionTo(mDnsCheckingState); + } + return HANDLED; + } + return NOT_HANDLED; + } + + /** + * Times a dns check with an interval based on {@link #curSignalStable} + */ + private void triggerSingleDnsCheck() { + long waitInterval; + if (signalUnstable) { + waitInterval = MIN_LOW_SIGNAL_CHECK_INTERVAL_MS; + unstableSignalChecks = true; + } else { + waitInterval = MIN_SINGLE_DNS_CHECK_INTERVAL_MS; + } + sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0), + waitTime(waitInterval, lastCheckTime)); + } + } + + class DnsCheckFailureState extends State { + @Override + public void enter() { + obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget(); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what != MESSAGE_HANDLE_BAD_AP) { + return NOT_HANDLED; + } + + if (msg.arg1 != mNetEventCounter) { + if (VDBG) { + Slog.v(WWSM_TAG, "Msg out of sync, ignoring..."); + } + return HANDLED; + } + + if (mDisableAPNextFailure || mNumFullDNSchecks >= MAX_CHECKS_PER_SSID) { + // TODO : Unban networks if they had low signal ? + Slog.i(WWSM_TAG, "Disabling current SSID " + wifiInfoToStr(mInitialConnInfo) + + ". " + + "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size()); + mWifiManager.disableNetwork(mInitialConnInfo.getNetworkId()); + transitionTo(mNotConnectedState); + } else { + Slog.i(WWSM_TAG, "Blacklisting current BSSID. " + wifiInfoToStr(mInitialConnInfo) + + "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size()); + + mWifiManager.addToBlacklist(mInitialConnInfo.getBSSID()); + mWifiManager.reassociate(); + transitionTo(mBlacklistedApState); + } + return HANDLED; + } + } + + class WalledGardenState extends State { + @Override + public void enter() { + obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget(); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) { + return NOT_HANDLED; + } + + if (msg.arg1 != mNetEventCounter) { + if (VDBG) { + Slog.v(WWSM_TAG, "WalledGardenState::Msg out of sync, ignoring..."); + } + return HANDLED; + } + popUpBrowser(); + transitionTo(mOnlineWatchState); + return HANDLED; + } + } + + class BlacklistedApState extends State { + @Override + public void enter() { + mDisableAPNextFailure = true; + sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0), + BLACKLIST_FOLLOWUP_INTERVAL_MS); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what != MESSAGE_NETWORK_FOLLOWUP) { + return NOT_HANDLED; + } + + if (msg.arg1 != mNetEventCounter) { + if (VDBG) { + Slog.v(WWSM_TAG, "BlacklistedApState::Msg out of sync, ignoring..."); + } + return HANDLED; + } + + transitionTo(mDnsCheckingState); + return HANDLED; + } + } +} |