Merge "Import revised translations."
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index d25de97..12a4dbb 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -235,7 +235,7 @@
             PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
             PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
             PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
-            defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder(this,
+            defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
                     pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
             defaultChangeIn.setDuration(DEFAULT_DURATION);
             defaultChangeIn.setStartDelay(mChangingAppearingDelay);
@@ -244,11 +244,11 @@
             defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
             defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
 
-            defaultFadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
+            defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
             defaultFadeIn.setDuration(DEFAULT_DURATION);
             defaultFadeIn.setStartDelay(mAppearingDelay);
             defaultFadeIn.setInterpolator(mAppearingInterpolator);
-            defaultFadeOut = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
+            defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
             defaultFadeOut.setDuration(DEFAULT_DURATION);
             defaultFadeOut.setStartDelay(mDisappearingDelay);
             defaultFadeOut.setInterpolator(mDisappearingInterpolator);
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
index b8919c2..7133d3a 100644
--- a/core/java/android/preference/SeekBarPreference.java
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -77,6 +77,11 @@
     }
 
     @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getInt(index, 0);
+    }
+
+    @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_UP) {
             if (keyCode == KeyEvent.KEYCODE_PLUS
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 761007f..d584acd 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1783,6 +1783,20 @@
     }
 
     /**
+     * @hide
+     */
+    public void setProperty(String key, String value) {
+        mWebView.nativeSetProperty(key, value);
+    }
+
+    /**
+     * @hide
+     */
+    public String getProperty(String key) {
+        return mWebView.nativeGetProperty(key);
+    }
+
+    /**
      * Transfer messages from the queue to the new WebCoreThread. Called from
      * WebCore thread.
      */
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3ae10fe..b22c57b0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -9289,4 +9289,6 @@
      */
     private native boolean  nativeScrollLayer(int layer, int newX, int newY);
     private native int      nativeGetBackgroundColor();
+    native void     nativeSetProperty(String key, String value);
+    native String   nativeGetProperty(String key);
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ecbd997..5a5fae4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6586,11 +6586,11 @@
                     scrollx = left;
                 }
             }
-        } else if (a == Layout.Alignment.ALIGN_LEFT) {
-            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
-        } else { // a == Layout.Alignment.ALIGN_RIGHT
+        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
             int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
             scrollx = right - hspace;
+        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
+            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
         }
 
         if (ht < vspace) {
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index b754d94..69b80d9 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -40,6 +40,7 @@
 
     /** 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/core/jni/Android.mk b/core/jni/Android.mk
index 58e7c8d..06dc083 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -145,7 +145,6 @@
 	android_server_Watchdog.cpp \
 	android_ddm_DdmHandleNativeHeap.cpp \
 	com_android_internal_os_ZygoteInit.cpp \
-	com_android_internal_graphics_NativeUtils.cpp \
 	android_backup_BackupDataInput.cpp \
 	android_backup_BackupDataOutput.cpp \
 	android_backup_FileBackupHelperBase.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 23c6da7..08431cd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -115,7 +115,6 @@
 extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
 extern int register_android_graphics_Xfermode(JNIEnv* env);
 extern int register_android_graphics_PixelFormat(JNIEnv* env);
-extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
 extern int register_android_view_Display(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
@@ -1147,7 +1146,6 @@
     REG_JNI(register_android_graphics_Typeface),
     REG_JNI(register_android_graphics_Xfermode),
     REG_JNI(register_android_graphics_YuvImage),
-    REG_JNI(register_com_android_internal_graphics_NativeUtils),
 
     REG_JNI(register_android_database_CursorWindow),
     REG_JNI(register_android_database_SQLiteCompiledSql),
diff --git a/core/jni/com_android_internal_graphics_NativeUtils.cpp b/core/jni/com_android_internal_graphics_NativeUtils.cpp
deleted file mode 100644
index 9cc43606..0000000
--- a/core/jni/com_android_internal_graphics_NativeUtils.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-#define LOG_TAG "AWT"
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "GraphicsJNI.h"
-#include <android_runtime/AndroidRuntime.h>
-
-#include "SkCanvas.h"
-#include "SkDevice.h"
-#include "SkPicture.h"
-#include "SkTemplates.h"
-
-namespace android
-{
-
-static jboolean scrollRect(JNIEnv* env, jobject graphics2D, jobject canvas, jobject rect, int dx, int dy) {
-    if (canvas == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return false;
-    }
-
-    SkIRect src, *srcPtr = NULL;
-    if (NULL != rect) {
-        GraphicsJNI::jrect_to_irect(env, rect, &src);
-        srcPtr = &src;
-    }
-    SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
-    const SkBitmap& bitmap = c->getDevice()->accessBitmap(true);
-    return bitmap.scrollRect(srcPtr, dx, dy, NULL);
-}
-
-static JNINativeMethod method_table[] = {
-    { "nativeScrollRect",
-      "(Landroid/graphics/Canvas;Landroid/graphics/Rect;II)Z",
-      (void*)scrollRect}
-};
-
-int register_com_android_internal_graphics_NativeUtils(JNIEnv *env) {
-    return AndroidRuntime::registerNativeMethods(
-        env, "com/android/internal/graphics/NativeUtils",
-        method_table, NELEM(method_table));
-}
-
-}
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
new file mode 100644
index 0000000..c0d9153
--- /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 d222c0b..57a1bab 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -30,4 +30,6 @@
     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 0000000..8d8d020
--- /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 0000000..fe51fd27
--- /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/graphics/java/com/android/internal/graphics/NativeUtils.java b/graphics/java/com/android/internal/graphics/NativeUtils.java
deleted file mode 100644
index c91b7d9..0000000
--- a/graphics/java/com/android/internal/graphics/NativeUtils.java
+++ /dev/null
@@ -1,40 +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 com.android.internal.graphics;
-
-import android.graphics.Canvas;
-import android.graphics.Rect;
-
-public final class NativeUtils {
-    /**
-     * This class is uninstantiable.
-     */
-    private NativeUtils() {
-        // This space intentionally left blank.
-    }
-
-    /**
-     * Scroll a rectangular portion of a canvas.
-     * 
-     * @param canvas the canvas to manipulate
-     * @param src the source rectangle
-     * @param dx horizontal offset
-     * @param dy vertical offset
-     */
-    public static native boolean nativeScrollRect(Canvas canvas, Rect src,
-            int dx, int dy);
-}
diff --git a/include/cpustats/CentralTendencyStatistics.h b/include/cpustats/CentralTendencyStatistics.h
new file mode 100644
index 0000000..21b6981
--- /dev/null
+++ b/include/cpustats/CentralTendencyStatistics.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#ifndef _CENTRAL_TENDENCY_STATISTICS_H
+#define _CENTRAL_TENDENCY_STATISTICS_H
+
+#include <math.h>
+
+// Not multithread safe
+class CentralTendencyStatistics {
+
+public:
+
+    CentralTendencyStatistics() :
+            mMean(NAN), mMedian(NAN), mMinimum(INFINITY), mMaximum(-INFINITY), mN(0), mM2(0),
+            mVariance(NAN), mVarianceKnownForN(0), mStddev(NAN), mStddevKnownForN(0) { }
+
+    ~CentralTendencyStatistics() { }
+
+    // add x to the set of samples
+    void sample(double x);
+
+    // return the arithmetic mean of all samples so far
+    double mean() const { return mMean; }
+
+    // return the minimum of all samples so far
+    double minimum() const { return mMinimum; }
+
+    // return the maximum of all samples so far
+    double maximum() const { return mMaximum; }
+
+    // return the variance of all samples so far
+    double variance() const;
+
+    // return the standard deviation of all samples so far
+    double stddev() const;
+
+    // return the number of samples added so far
+    unsigned n() const { return mN; }
+
+    // reset the set of samples to be empty
+    void reset();
+
+private:
+    double mMean;
+    double mMedian;
+    double mMinimum;
+    double mMaximum;
+    unsigned mN;    // number of samples so far
+    double mM2;
+
+    // cached variance, and n at time of caching
+    mutable double mVariance;
+    mutable unsigned mVarianceKnownForN;
+
+    // cached standard deviation, and n at time of caching
+    mutable double mStddev;
+    mutable unsigned mStddevKnownForN;
+
+};
+
+#endif // _CENTRAL_TENDENCY_STATISTICS_H
diff --git a/include/cpustats/README.txt b/include/cpustats/README.txt
new file mode 100644
index 0000000..14439f0
--- /dev/null
+++ b/include/cpustats/README.txt
@@ -0,0 +1,6 @@
+This is a static library of CPU usage statistics, originally written
+for audio but most are not actually specific to audio.
+
+Requirements to be here:
+ * should be related to CPU usage statistics
+ * should be portable to host; avoid Android OS dependencies without a conditional
diff --git a/include/cpustats/ThreadCpuUsage.h b/include/cpustats/ThreadCpuUsage.h
new file mode 100644
index 0000000..24012a4
--- /dev/null
+++ b/include/cpustats/ThreadCpuUsage.h
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#ifndef _THREAD_CPU_USAGE_H
+#define _THREAD_CPU_USAGE_H
+
+#include <cpustats/CentralTendencyStatistics.h>
+
+// Track CPU usage for the current thread, and maintain statistics on
+// the CPU usage.  Units are in per-thread CPU ns, as reported by
+// clock_gettime(CLOCK_THREAD_CPUTIME_ID).  Simple usage: for cyclic
+// threads where you want to measure the execution time of the whole
+// cycle, just call sampleAndEnable() at the start of each cycle.
+// Then call statistics() to get the results, and resetStatistics()
+// to start a new set of measurements.
+// For acyclic threads, or for cyclic threads where you want to measure
+// only part of each cycle, call enable(), disable(), and/or setEnabled()
+// to demarcate the region(s) of interest, and then call sample() periodically.
+// This class is not thread-safe for concurrent calls from multiple threads;
+// the methods of this class may only be called by the current thread
+// which constructed the object.
+
+class ThreadCpuUsage
+{
+
+public:
+    ThreadCpuUsage() :
+        mIsEnabled(false),
+        mWasEverEnabled(false),
+        mAccumulator(0),
+        // mPreviousTs
+        // mMonotonicTs
+        mMonotonicKnown(false)
+        // mStatistics
+        { }
+
+    ~ThreadCpuUsage() { }
+
+    // Return whether currently tracking CPU usage by current thread
+    bool isEnabled()    { return mIsEnabled; }
+
+    // Enable tracking of CPU usage by current thread;
+    // any CPU used from this point forward will be tracked.
+    // Returns the previous enabled status.
+    bool enable()       { return setEnabled(true); }
+
+    // Disable tracking of CPU usage by current thread;
+    // any CPU used from this point forward will be ignored.
+    // Returns the previous enabled status.
+    bool disable()      { return setEnabled(false); }
+
+    // Set the enabled status and return the previous enabled status.
+    // This method is intended to be used for safe nested enable/disabling.
+    bool setEnabled(bool isEnabled);
+
+    // Add a sample point for central tendency statistics, and also
+    // enable tracking if needed.  If tracking has never been enabled, then
+    // enables tracking but does not add a sample (it is not possible to add
+    // a sample the first time because no previous).  Otherwise if tracking is
+    // enabled, then adds a sample for tracked CPU ns since the previous
+    // sample, or since the first call to sampleAndEnable(), enable(), or
+    // setEnabled(true).  If there was a previous sample but tracking is
+    // now disabled, then adds a sample for the tracked CPU ns accumulated
+    // up until the most recent disable(), resets this accumulator, and then
+    // enables tracking.  Calling this method rather than enable() followed
+    // by sample() avoids a race condition for the first sample.
+    void sampleAndEnable();
+
+    // Add a sample point for central tendency statistics, but do not
+    // change the tracking enabled status.  If tracking has either never been
+    // enabled, or has never been enabled since the last sample, then log a warning
+    // and don't add sample.  Otherwise, adds a sample for tracked CPU ns since
+    // the previous sample or since the first call to sampleAndEnable(),
+    // enable(), or setEnabled(true) if no previous sample.
+    void sample();
+
+    // Return the elapsed delta wall clock ns since initial enable or statistics reset,
+    // as reported by clock_gettime(CLOCK_MONOTONIC).
+    long long elapsed() const;
+
+    // Reset statistics and elapsed.  Has no effect on tracking or accumulator.
+    void resetStatistics();
+
+    // Return a const reference to the central tendency statistics.
+    // Note that only the const methods can be called on this object.
+    const CentralTendencyStatistics& statistics() const {
+        return mStatistics;
+    }
+
+private:
+    bool mIsEnabled;                // whether tracking is currently enabled
+    bool mWasEverEnabled;           // whether tracking was ever enabled
+    long long mAccumulator;         // accumulated thread CPU time since last sample, in ns
+    struct timespec mPreviousTs;    // most recent thread CPU time, valid only if mIsEnabled is true
+    struct timespec mMonotonicTs;   // most recent monotonic time
+    bool mMonotonicKnown;           // whether mMonotonicTs has been set
+    CentralTendencyStatistics mStatistics;
+};
+
+#endif //  _THREAD_CPU_USAGE_H
diff --git a/libs/cpustats/Android.mk b/libs/cpustats/Android.mk
new file mode 100644
index 0000000..5c1074d
--- /dev/null
+++ b/libs/cpustats/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES :=     \
+        CentralTendencyStatistics.cpp \
+        ThreadCpuUsage.cpp
+
+LOCAL_MODULE := libcpustats
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES :=     \
+        CentralTendencyStatistics.cpp \
+        ThreadCpuUsage.cpp
+
+LOCAL_MODULE := libcpustats
+
+include $(BUILD_HOST_STATIC_LIBRARY)
diff --git a/libs/cpustats/CentralTendencyStatistics.cpp b/libs/cpustats/CentralTendencyStatistics.cpp
new file mode 100644
index 0000000..42ab62b
--- /dev/null
+++ b/libs/cpustats/CentralTendencyStatistics.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+
+#include <cpustats/CentralTendencyStatistics.h>
+
+void CentralTendencyStatistics::sample(double x)
+{
+    // update min and max
+    if (x < mMinimum)
+        mMinimum = x;
+    if (x > mMaximum)
+        mMaximum = x;
+    // Knuth
+    if (mN == 0) {
+        mMean = 0;
+    }
+    ++mN;
+    double delta = x - mMean;
+    mMean += delta / mN;
+    mM2 += delta * (x - mMean);
+}
+
+void CentralTendencyStatistics::reset()
+{
+    mMean = NAN;
+    mMedian = NAN;
+    mMinimum = INFINITY;
+    mMaximum = -INFINITY;
+    mN = 0;
+    mM2 = 0;
+    mVariance = NAN;
+    mVarianceKnownForN = 0;
+    mStddev = NAN;
+    mStddevKnownForN = 0;
+}
+
+double CentralTendencyStatistics::variance() const
+{
+    double variance;
+    if (mVarianceKnownForN != mN) {
+        if (mN > 1) {
+            // double variance_n = M2/n;
+            variance = mM2 / (mN - 1);
+        } else {
+            variance = NAN;
+        }
+        mVariance = variance;
+        mVarianceKnownForN = mN;
+    } else {
+        variance = mVariance;
+    }
+    return variance;
+}
+
+double CentralTendencyStatistics::stddev() const
+{
+    double stddev;
+    if (mStddevKnownForN != mN) {
+        stddev = sqrt(variance());
+        mStddev = stddev;
+        mStddevKnownForN = mN;
+    } else {
+        stddev = mStddev;
+    }
+    return stddev;
+}
diff --git a/libs/cpustats/ThreadCpuUsage.cpp b/libs/cpustats/ThreadCpuUsage.cpp
new file mode 100644
index 0000000..4bfbdf3
--- /dev/null
+++ b/libs/cpustats/ThreadCpuUsage.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <time.h>
+
+#include <utils/Log.h>
+
+#include <cpustats/ThreadCpuUsage.h>
+
+bool ThreadCpuUsage::setEnabled(bool isEnabled)
+{
+    bool wasEnabled = mIsEnabled;
+    // only do something if there is a change
+    if (isEnabled != wasEnabled) {
+        int rc;
+        // enabling
+        if (isEnabled) {
+            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mPreviousTs);
+            if (rc) {
+                LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+                isEnabled = false;
+            } else {
+                mWasEverEnabled = true;
+                // record wall clock time at first enable
+                if (!mMonotonicKnown) {
+                    rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+                    if (rc) {
+                        LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+                    } else {
+                        mMonotonicKnown = true;
+                    }
+                }
+            }
+        // disabling
+        } else {
+            struct timespec ts;
+            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+            if (rc) {
+                LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+            } else {
+                long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+                        (ts.tv_nsec - mPreviousTs.tv_nsec);
+                mAccumulator += delta;
+#if 0
+                mPreviousTs = ts;
+#endif
+            }
+        }
+        mIsEnabled = isEnabled;
+    }
+    return wasEnabled;
+}
+
+void ThreadCpuUsage::sampleAndEnable()
+{
+    bool wasEverEnabled = mWasEverEnabled;
+    if (enable()) {
+        // already enabled, so add a new sample relative to previous
+        sample();
+    } else if (wasEverEnabled) {
+        // was disabled, but add sample for accumulated time while enabled
+        mStatistics.sample((double) mAccumulator);
+        mAccumulator = 0;
+    }
+}
+
+void ThreadCpuUsage::sample()
+{
+    if (mWasEverEnabled) {
+        if (mIsEnabled) {
+            struct timespec ts;
+            int rc;
+            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+            if (rc) {
+                LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+            } else {
+                long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+                        (ts.tv_nsec - mPreviousTs.tv_nsec);
+                mAccumulator += delta;
+                mPreviousTs = ts;
+            }
+        } else {
+            mWasEverEnabled = false;
+        }
+        mStatistics.sample((double) mAccumulator);
+        mAccumulator = 0;
+    } else {
+        LOGW("Can't add sample because measurements have never been enabled");
+    }
+}
+
+long long ThreadCpuUsage::elapsed() const
+{
+    long long elapsed;
+    if (mMonotonicKnown) {
+        struct timespec ts;
+        int rc;
+        rc = clock_gettime(CLOCK_MONOTONIC, &ts);
+        if (rc) {
+            LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+            elapsed = 0;
+        } else {
+            // mMonotonicTs is updated only at first enable and resetStatistics
+            elapsed = (ts.tv_sec - mMonotonicTs.tv_sec) * 1000000000LL +
+                    (ts.tv_nsec - mMonotonicTs.tv_nsec);
+        }
+    } else {
+        LOGW("Can't compute elapsed time because measurements have never been enabled");
+        elapsed = 0;
+    }
+    return elapsed;
+}
+
+void ThreadCpuUsage::resetStatistics()
+{
+    mStatistics.reset();
+    if (mMonotonicKnown) {
+        int rc;
+        rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+        if (rc) {
+            LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+            mMonotonicKnown = false;
+        }
+    }
+}
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index 88433fb..9abe89d 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "SurfaceTexture_test"
 //#define LOG_NDEBUG 0
 
 #include <gtest/gtest.h>
@@ -379,6 +380,13 @@
         ASSERT_NE(-1, mTexMatrixHandle);
     }
 
+    virtual void TearDown() {
+        mANW.clear();
+        mSTC.clear();
+        mST.clear();
+        GLTest::TearDown();
+    }
+
     // drawTexture draws the SurfaceTexture over the entire GL viewport.
     void drawTexture() {
         const GLfloat triangleVertices[] = {
@@ -1089,13 +1097,21 @@
     // synchronously from SurfaceTexture::queueBuffer.
     class FrameCondition : public SurfaceTexture::FrameAvailableListener {
     public:
+        FrameCondition():
+                mFrameAvailable(false),
+                mFrameFinished(false) {
+        }
+
         // waitForFrame waits for the next frame to arrive.  This should be
         // called from the consumer thread once for every frame expected by the
         // test.
         void waitForFrame() {
-            LOGV("+waitForFrame");
             Mutex::Autolock lock(mMutex);
-            status_t result = mFrameAvailableCondition.wait(mMutex);
+            LOGV("+waitForFrame");
+            while (!mFrameAvailable) {
+                mFrameAvailableCondition.wait(mMutex);
+            }
+            mFrameAvailable = false;
             LOGV("-waitForFrame");
         }
 
@@ -1103,22 +1119,30 @@
         // on to produce the next frame.  This should be called by the consumer
         // thread once for every frame expected by the test.
         void finishFrame() {
-            LOGV("+finishFrame");
             Mutex::Autolock lock(mMutex);
+            LOGV("+finishFrame");
+            mFrameFinished = true;
             mFrameFinishCondition.signal();
             LOGV("-finishFrame");
         }
 
         // This should be called by SurfaceTexture on the producer thread.
         virtual void onFrameAvailable() {
-            LOGV("+onFrameAvailable");
             Mutex::Autolock lock(mMutex);
+            LOGV("+onFrameAvailable");
+            mFrameAvailable = true;
             mFrameAvailableCondition.signal();
-            mFrameFinishCondition.wait(mMutex);
+            while (!mFrameFinished) {
+                mFrameFinishCondition.wait(mMutex);
+            }
+            mFrameFinished = false;
             LOGV("-onFrameAvailable");
         }
 
     protected:
+        bool mFrameAvailable;
+        bool mFrameFinished;
+
         Mutex mMutex;
         Condition mFrameAvailableCondition;
         Condition mFrameFinishCondition;
@@ -1164,6 +1188,7 @@
         }
         mProducerThread.clear();
         mFC.clear();
+        SurfaceTextureGLTest::TearDown();
     }
 
     void runProducerThread(const sp<ProducerThread> producerThread) {
diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp
index 0d0d6c2..2091381 100644
--- a/media/libstagefright/XINGSeeker.cpp
+++ b/media/libstagefright/XINGSeeker.cpp
@@ -24,8 +24,8 @@
 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 @@
 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 8ecc17c..ca61b3d 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 @@
       mDurationUs(-1),
       mSeekDone(false),
       mDisconnectPending(false),
-      mMonitorQueueGeneration(0) {
+      mMonitorQueueGeneration(0),
+      mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) {
     if (mUIDValid) {
         mHTTPDataSource->setUID(mUID);
     }
@@ -175,7 +175,8 @@
 
     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 @@
     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 @@
         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 @@
     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 @@
 
     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 @@
 
         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 188ef5e..116ed0e 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -62,8 +62,6 @@
         kMaxNumRetries         = 5,
     };
 
-    static const int64_t kMaxPlaylistAgeUs;
-
     enum {
         kWhatConnect        = 'conn',
         kWhatDisconnect     = 'disc',
@@ -106,6 +104,16 @@
 
     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 @@
     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 @@
 
     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 d5a484e..ec5bd9b 100644
--- a/media/libstagefright/include/XINGSeeker.h
+++ b/media/libstagefright/include/XINGSeeker.h
@@ -37,7 +37,7 @@
     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/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
index 59783e5..4d517db 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
@@ -91,8 +91,12 @@
         assertEquals("AudioType Mismatch ", audioCodecType, mvi.getAudioType());
         assertEquals("Audio Sampling " + mvi.getAudioSamplingFrequency(),
             audioSamplingFrequency, mvi.getAudioSamplingFrequency());
-        assertEquals("Audio Channels " + mvi.getAudioChannels(), audioChannel,
-            mvi.getAudioChannels());
+        // PV SW AAC codec always returns number of channels as Stereo.
+        // So we do not assert for number of audio channels for AAC_LC
+        if ( audioCodecType != MediaProperties.ACODEC_AAC_LC ) {
+            assertEquals("Audio Channels " + mvi.getAudioChannels(), audioChannel,
+                mvi.getAudioChannels());
+        }
     }
 
     protected void validateAudioProperties(int audioCodecType, int duration,
@@ -103,8 +107,12 @@
             duration, aT.getDuration(), 10));
         assertEquals("Audio Sampling " + aT.getAudioSamplingFrequency(),
             audioSamplingFrequency, aT.getAudioSamplingFrequency());
-        assertEquals("Audio Channels " + aT.getAudioChannels(), audioChannel,
-            aT.getAudioChannels());
+        // PV SW AAC codec always returns number of channels as Stereo.
+        // So we do not assert for number of audio channels for AAC_LC
+        if ( audioCodecType != MediaProperties.ACODEC_AAC_LC ) {
+            assertEquals("Audio Channels " + aT.getAudioChannels(), audioChannel,
+                aT.getAudioChannels());
+        }
     }
 
     protected void validateImageProperties(int aspectRatio, int fileType,
diff --git a/packages/VpnDialogs/res/layout/confirm.xml b/packages/VpnDialogs/res/layout/confirm.xml
index 5ab6ee2..11a247a 100644
--- a/packages/VpnDialogs/res/layout/confirm.xml
+++ b/packages/VpnDialogs/res/layout/confirm.xml
@@ -18,7 +18,7 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="5mm">
+        android:padding="3mm">
 
     <ImageView android:id="@+id/icon"
             android:layout_width="@android:dimen/app_icon_size"
diff --git a/packages/VpnDialogs/res/layout/manage.xml b/packages/VpnDialogs/res/layout/manage.xml
index 330b8e3..3dcbb46 100644
--- a/packages/VpnDialogs/res/layout/manage.xml
+++ b/packages/VpnDialogs/res/layout/manage.xml
@@ -18,6 +18,7 @@
 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:padding="3mm"
         android:stretchColumns="0,1">
 
     <TableRow>
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 6bb1f56..a0407b9 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -24,6 +24,7 @@
     libdl
 
 LOCAL_STATIC_LIBRARIES := \
+    libcpustats \
     libmedia_helper
 
 LOCAL_MODULE:= libaudioflinger
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index daf94f2..86d4cc3 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -52,6 +52,9 @@
 #include <media/EffectsFactoryApi.h>
 #include <audio_effects/effect_visualizer.h>
 
+#include <cpustats/ThreadCpuUsage.h>
+// #define DEBUG_CPU_USAGE 10  // log statistics every n wall clock seconds
+
 // ----------------------------------------------------------------------------
 
 
@@ -1529,9 +1532,40 @@
     uint32_t idleSleepTime = idleSleepTimeUs();
     uint32_t sleepTime = idleSleepTime;
     Vector< sp<EffectChain> > effectChains;
+#ifdef DEBUG_CPU_USAGE
+    ThreadCpuUsage cpu;
+    const CentralTendencyStatistics& stats = cpu.statistics();
+#endif
 
     while (!exitPending())
     {
+#ifdef DEBUG_CPU_USAGE
+        cpu.sampleAndEnable();
+        unsigned n = stats.n();
+        // cpu.elapsed() is expensive, so don't call it every loop
+        if ((n & 127) == 1) {
+            long long elapsed = cpu.elapsed();
+            if (elapsed >= DEBUG_CPU_USAGE * 1000000000LL) {
+                double perLoop = elapsed / (double) n;
+                double perLoop100 = perLoop * 0.01;
+                double mean = stats.mean();
+                double stddev = stats.stddev();
+                double minimum = stats.minimum();
+                double maximum = stats.maximum();
+                cpu.resetStatistics();
+                LOGI("CPU usage over past %.1f secs (%u mixer loops at %.1f mean ms per loop):\n  us per mix loop: mean=%.0f stddev=%.0f min=%.0f max=%.0f\n  %% of wall: mean=%.1f stddev=%.1f min=%.1f max=%.1f",
+                        elapsed * .000000001, n, perLoop * .000001,
+                        mean * .001,
+                        stddev * .001,
+                        minimum * .001,
+                        maximum * .001,
+                        mean / perLoop100,
+                        stddev / perLoop100,
+                        minimum / perLoop100,
+                        maximum / perLoop100);
+            }
+        }
+#endif
         processConfigEvents();
 
         mixerStatus = MIXER_IDLE;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 168b894..e9e66cb 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -87,8 +87,10 @@
 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 @@
     // 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 @@
                 }
             }
 
-            // 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 @@
                 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 @@
                 } 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 @@
             }
         }
 
+        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 5f0922e..7112553 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.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 @@
      * 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 @@
                 (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 @@
         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 b0881a4..680814c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1551,8 +1551,18 @@
          * 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/tests/BiDiTests/res/layout/basic.xml b/tests/BiDiTests/res/layout/basic.xml
index d438b2c..c1ebd20 100644
--- a/tests/BiDiTests/res/layout/basic.xml
+++ b/tests/BiDiTests/res/layout/basic.xml
@@ -40,7 +40,7 @@
                       android:textSize="32dip"
                       android:text="@string/textview_text"
                     />
-    
+
             <EditText android:id="@+id/edittext"
                       android:layout_height="wrap_content"
                       android:layout_width="match_parent"
@@ -51,4 +51,32 @@
 
     </LinearLayout>
 
+    <LinearLayout
+        android:layout_width="600dip"
+        android:layout_height="128dip"
+        android:layout_gravity="center_vertical"
+        android:orientation="horizontal"
+        style="@android:style/Widget.Holo.Spinner"
+        >
+        <LinearLayout
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical"
+            android:orientation="vertical"
+            >
+            <TextView
+                android:id="@+id/spinner_line_1"
+                android:layout_width="match_parent"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:textSize="16dip"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:gravity="left|center_vertical"
+                android:text="@string/button_text"
+                />
+        </LinearLayout>
+    </LinearLayout>
+
 </FrameLayout>
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiWatchdogService.java b/wifi/java/android/net/wifi/WifiWatchdogService.java
deleted file mode 100644
index bce4b3a..0000000
--- 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 0000000..0eb73b7
--- /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;
+        }
+    }
+}